Astro contact form, straight to email
Astro ships almost no JavaScript, and a .astro page renders to static HTML, which means a contact form has nothing on the server to catch its submission. You can add an SSR adapter or Astro Actions, but you still need email, spam filtering, and storage to live somewhere. The simplest path keeps the page static and points the form at an endpoint that provides all three.
By default an Astro contact form is plain static HTML with no server behind it. Set the form’s action to a MakeTheForm endpoint and method to POST: the browser sends the submission, the endpoint filters spam, emails it to you, and saves it to a searchable inbox. Works on any static host, zero JavaScript required.
The minimal integration
Drop this HTML straight into your .astro component. Because the form posts to an external endpoint, the page stays fully static: no SSR adapter, no server output mode. The visitor lands on a branded success page you can redirect in settings.
<form action="https://mtform.co/f/your-form-key" method="POST">
<label>
Name
<input name="name" autocomplete="name" required>
</label>
<label>
Email
<input type="email" name="email" autocomplete="email" required>
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<!-- Spam trap: hidden from people, tempting to bots. Leave it empty. -->
<input
type="text"
name="_mtf_honeypot"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
style="position:absolute;left:-9999px"
>
<button type="submit">Send</button>
</form>Replace your existing <form> tag, or just change its action attribute. The browser navigates to a branded success page. Configure a redirect in Settings.
Step by step
- 01
Create the endpoint
Create a MakeTheForm form to get your public endpoint URL and inbox.
- 02
Point the form’s action at it
In your .astro component, set the <form> action to the endpoint and method to POST. Include the honeypot field named exactly _mtf_honeypot.
- 03
Keep it static, or go AJAX
Leave the page static for a full-page success redirect, or add the small client script above to show an inline message without reloading.
- 04
Verify and deploy
Verify your recipient email once, deploy to Netlify, Vercel, Cloudflare Pages, or GitHub Pages, and submit to confirm delivery.
Prefer to keep the visitor on the page? Add a small client script to the same .astro component. It intercepts the submit, posts JSON to the same endpoint, and shows an inline message, while the plain-HTML form still works if JS fails.
---
// src/components/Contact.astro
const endpoint = 'https://mtform.co/f/your-form-key';
---
<form id="contact" action={endpoint} method="POST">
<input name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<input name="_mtf_honeypot" tabindex="-1" autocomplete="off"
aria-hidden="true" style="position:absolute;left:-9999px" />
<button type="submit">Send</button>
<p id="status" role="status"></p>
</form>
<script>
const form = document.getElementById('contact') as HTMLFormElement;
const status = document.getElementById('status')!;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const button = form.querySelector('button')!;
button.disabled = true;
try {
const res = await fetch(form.action, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form))),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error?.message ?? 'Unable to send.');
form.reset();
status.textContent = 'Thanks. We got your message.';
} catch (err) {
// Values stay put; we only reset on success.
status.textContent = (err as Error).message;
} finally {
button.disabled = false;
}
});
</script>Common pitfalls
Adding an SSR adapter just to catch a form
It’s tempting to switch Astro to server output and write an endpoint under src/pages/api. That turns a static site into a running server you deploy and maintain. Posting to an external endpoint keeps the whole site static.
Astro Actions still need a destination
Astro Actions handle the wiring, but they don’t send email, filter spam, or store anything. That logic is yours to write and host. If email plus spam plus a searchable inbox is all you want, an endpoint is less to build than an Action plus a mail service.
Troubleshooting
| Symptom | What is happening | Fix |
|---|---|---|
| full page reload | The plain HTML form does a normal browser POST and navigates away. | That’s expected: configure a redirect in settings, or use the client-script version to stay on the page. |
| script does nothing | The <script> ran before the form existed, or lived in a different file. | Keep the script in the same .astro file as the form; Astro bundles component scripts and runs them after parse. |
| 403 | Allowed-domains is on and your deployed Astro domain isn’t listed. | Add your production domain in the form’s spam settings, or leave the form public. |
Frequently asked questions
→How do I add a contact form to an Astro site?
Put a normal <form> in a .astro component, set its action to a form endpoint and method to POST, and include a honeypot field. The endpoint receives the submission, filters spam, and emails it to you: no server code in Astro.
→Can an Astro static site send form submissions to email?
Yes. A static Astro build can’t email anything itself, but its form can POST to a hosted endpoint that does. You keep the site fully static and still get email delivery, spam filtering, and stored submissions.
→Do I need Astro Actions or an SSR adapter for a form?
Not for a contact form. Actions and SSR let you run server code, but you’d still have to send email and filter spam yourself. Pointing the form at an endpoint keeps the site static and covers all of that.
Keep going
Last reviewed July 23, 2026
Ship this form for real
Create an endpoint, paste it in, and watch the first submission land in your inbox, in under three minutes.
Create free endpoint