Astro contact form to email
Astro sites are often fully static, which is exactly where FormRoute helps: the form posts from the browser to FormRoute, so you don't need an adapter, an endpoint or a server.
You need a FormRoute account with a verified domain and a form whose fields are name, email and message, or change the names below to match yours. Getting started covers the setup.
1. Add your site key
Variables that start with PUBLIC_ are available in components. The site key is public by design.
PUBLIC_FORMROUTE_SITE_KEY=fr_your_site_key2. Create the component
Astro bundles the <script> and runs it in the browser. The site key travels to it in a data- attribute.
---
// src/components/ContactForm.astro
const siteKey = import.meta.env.PUBLIC_FORMROUTE_SITE_KEY;
---
<form id="contact-form" data-site-key={siteKey}>
<label>Name <input name="name" required maxlength="120" /></label>
<label>Email <input name="email" type="email" required maxlength="254" /></label>
<label>Message <textarea name="message" required maxlength="5000"></textarea></label>
<!-- Honeypot: hidden from people, must stay empty -->
<div aria-hidden="true" style="position:absolute;left:-10000px">
<label>Leave this empty <input name="hp" tabindex="-1" autocomplete="off" /></label>
</div>
<button type="submit">Send</button>
<p id="contact-status" role="status"></p>
</form>
<script>
const ENDPOINT = "https://form-route.com/api/v1/send";
const form = document.querySelector<HTMLFormElement>("#contact-form")!;
const status = document.querySelector("#contact-status")!;
const shownAt = Date.now();
form.addEventListener("submit", async (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent = "Sending…";
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
siteKey: form.dataset.siteKey,
t: Date.now() - shownAt,
hp: data.get("hp") ?? "",
name: data.get("name"),
email: data.get("email"),
message: data.get("message"),
}),
});
status.textContent = response.ok
? "Thanks, your message was sent."
: "Sorry, that didn't go through. Please try again.";
if (response.ok) form.reset();
} catch {
status.textContent = "Sorry, that didn't go through. Please try again.";
}
});
</script>3. Use it on a page
---
import ContactForm from "../components/ContactForm.astro";
---
<h1>Contact us</h1>
<ContactForm />4. List your website in FormRoute
In your FormRoute account, open your form and add every address the form is served from to Allowed websites, for example https://www.example.com and, while you develop, http://localhost:4321. Each entry is a scheme and host, with a port only if your site uses one.
A browser submission from an address that isn't listed is refused, and your account shows it as Rejected: website not allowed. For local development, use the exact address your dev server prints.
5. Send a test
Open the page, fill in the form and send it. The submission appears on your FormRoute home page within seconds, as Sent or with the reason it was refused, and the email arrives from your own domain.
Fill the form in at a human pace: a submission sent within 1.2 seconds of the form appearing is treated as automated.
Good to know
- Deploying? Set the same environment variable in your host's settings (for example your Vercel or Netlify project):
.envfiles on your machine are not deployed. Or put the key in the component as a constant; it is public by design.
More guides
HTML · Next.js · React · Vue · WordPress · Webflow · Framer · Hugo, Jekyll and Eleventy