Framework // Astro

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.

The short answer

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.

HTML
<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

  1. 01

    Create the endpoint

    Create a MakeTheForm form to get your public endpoint URL and inbox.

  2. 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.

  3. 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.

  4. 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.

Contact.astro (AJAX, no page reload)
---
// 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

Troubleshooting by error code
SymptomWhat is happeningFix
full page reloadThe 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 nothingThe <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.
403Allowed-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.

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