Astro

Add a contact form to an Astro site and keep it fully static. A plain form post works with no JavaScript, and a small client script keeps visitors on the page.

5 minWorks on every planUpdated Sep 22, 2026

Before you start

You need a form endpoint from the dashboard (see the Quickstart) and an Astro project. Nothing here needs an adapter or server output: the form posts to MakeTheForm directly, so your site can stay fully static and deploy anywhere.

  1. 1

    Add the form

    An Astro page renders to plain HTML, so a normal form is all you need. Set action to your endpoint and method to POST, and give every input a name.

    src/pages/contact.astro
    ---
    // src/pages/contact.astro
    const endpoint = 'https://mtform.co/f/your-form-key';
    ---
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>Contact</title>
      </head>
      <body>
        <form action={endpoint} 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>
      </body>
    </html>
    Keep the spam trap as it is. The _mtf_honeypot input is positioned off-screen, not hidden with display: none, because bots skip fields they can tell are hidden. Leave it empty and never mark it required.
  2. 2

    Test it

    Run astro dev, open /contact and submit. The browser follows a redirect to a plain confirmation page, and the submission appears under Submissions in the dashboard.

    To send visitors back to your own thank-you page instead, add the redirect field described in HTML forms. Its target must be on one of the form's allowed domains.

  3. 3

    Optional: stay on the page

    To show the result without leaving the page, add a client script to the component. The form still has its action, so it works as a plain post if the script never runs.

    src/components/ContactForm.astro
    ---
    // src/components/ContactForm.astro
    const endpoint = 'https://mtform.co/f/your-form-key';
    ---
    <form id="contact-form" action={endpoint} 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>
    
      <input
        type="text"
        name="_mtf_honeypot"
        tabindex="-1"
        autocomplete="off"
        aria-hidden="true"
        style="position:absolute;left:-9999px"
      />
    
      <button type="submit">Send</button>
      <p id="contact-status" role="status"></p>
    </form>
    
    <script>
      const form = document.getElementById('contact-form') as HTMLFormElement;
      const status = document.getElementById('contact-status') as HTMLParagraphElement;
      const button = form.querySelector('button') as HTMLButtonElement;
    
      form.addEventListener('submit', async (event) => {
        event.preventDefault();
        button.disabled = true;
        status.textContent = '';
    
        try {
          const response = await fetch(form.action, {
            method: 'POST',
            headers: { Accept: 'application/json' },
            body: new FormData(form),
          });
          const result = await response.json();
    
          if (!response.ok) {
            throw new Error(result.error?.message ?? 'Unable to send your message.');
          }
    
          form.reset();
          status.textContent = 'Thanks. We got your message.';
        } catch (error) {
          // Entered values stay put; we only reset on success.
          status.textContent = error instanceof Error ? error.message : 'Unable to send your message.';
        } finally {
          button.disabled = false;
        }
      });
    </script>

    The script sends the form as FormData with Accept: application/json, so the endpoint answers with JSON instead of a redirect. Do not set a Content-Type yourself: the browser adds the right one, and sending FormData also means a file input is sent along with the other fields.

    Astro processes and bundles <script> tags in components and runs them after the page has parsed, so the form exists when the script looks it up. Keep the script in the same component as the form.

Next steps