An Astro contact form that works on a static build

Send an Astro contact form to your email from a fully static site, with no adapter, Astro Actions or email API. Zero JavaScript by default, plus an optional script to submit in place.

Free for 100 submissions a month, no credit card. Updated

Astro builds static HTML by default, and static HTML has nowhere to send a form. Astro Actions and API endpoints in src/pages/api both fix that by adding a server: an adapter, on-demand rendering, and an email provider with its key.

A Nisuform endpoint as the form's action keeps the site static. The browser posts the form to the endpoint, which filters spam, stores the submission and emails you. You keep output: 'static' and can host the dist folder anywhere.

The form

src/components/ContactForm.astro
---
const endpoint = 'https://api.nisuform.com/s/YOUR_FORM_KEY'
---

<form id="contact-form" action={endpoint} method="POST">
  <label>
    Name
    <input type="text" name="name" autocomplete="name" required />
  </label>
  <label>
    Email
    <input type="email" name="email" autocomplete="email" required />
  </label>
  <label>
    Message
    <textarea name="message" rows="5" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display: none" />
  <button type="submit">Send</button>
  <p role="status" aria-live="polite"></p>
</form>

That's a complete form with zero JavaScript. On submit, the browser shows the Nisuform thank-you page with a link back to your site. On Pro you can send visitors to your own thank-you page with a redirect.

Create a form to get the key for endpoint. Its Setup tab also has an Astro version with your own field names under Code.

Submitting without a reload

To keep visitors on the page, add this script to the same component. Astro bundles it and loads it once per page. If it never runs, the form still posts the normal way.

src/components/ContactForm.astro
<script>
  const form = document.querySelector<HTMLFormElement>('#contact-form')!
  const shownAt = Date.now()

  form.addEventListener('submit', async (event) => {
    event.preventDefault()
    const status = form.querySelector('[role="status"]')!
    const button = form.querySelector('button')!
    const data = new FormData(form)
    data.set('_elapsed', String(Date.now() - shownAt))
    button.disabled = true
    try {
      const response = await fetch(form.action, {
        method: 'POST',
        headers: { Accept: 'application/json' },
        body: data,
      })
      const result = await response.json()
      status.textContent = result.ok ? 'Thanks! We got your message.' : result.message
      if (result.ok) form.reset()
    } catch {
      status.textContent = 'Something went wrong. Please try again.'
    } finally {
      button.disabled = false
    }
  })
</script>

Accept: application/json gets a JSON answer instead of the thank-you page. _elapsed is how long the form was open, and submissions sent in under two seconds are dropped as bots.

With view transitions through Astro's ClientRouter, the script only runs on the first page load. Move the setup into document.addEventListener('astro:page-load', ...) so it runs after every navigation.

Islands

If your site already uses React, Vue or Svelte, you can render one of those components as an island with client:load instead. The React, Vue and Svelte versions all post to the same endpoint.

Adding an adapter later for other pages doesn't change the form. When the site is live, add its domain to the form's allowed websites so other sites can't post to your endpoint.

What you get with Nisuform

Free covers 100 submissions a month across 3 forms. Pro is $10 a month for 5,000 submissions and adds the features marked Pro.

  • Spam filtering on every plan

    A honeypot, a fill-time check, rate limits and duplicate checks run on every submission, with optional Cloudflare Turnstile or Google reCAPTCHA. Spam never counts toward your limit.

  • An email for every submission

    You get an email within seconds of each submission. Reply-to is set to the visitor, so you can answer from your mail app.

  • Every submission kept

    Search, star and export to CSV. Submissions stay until you delete them, so a missed email never loses a message.

  • File uploadsPro

    Up to 5 images or PDFs per submission, stored privately and shared through links that expire.

  • Slack, Discord, Sheets and webhooksPro

    Send each submission to Slack, Discord, Telegram or Google Sheets, or anywhere else with signed webhooks, Zapier or Make.

  • Auto-repliesPro

    Send each visitor a confirmation in your own words, from your own sender name.

Astro guides

Frequently asked questions

Can I use Astro Actions on a static site?

No. Actions run on the server, so they need an adapter and on-demand rendering. A form that posts to a form backend endpoint works on a fully static build, because the browser sends it straight to the endpoint.

Does the form need JavaScript?

No. A plain HTML form with the endpoint as its action posts on its own and shows a thank-you page. The optional script keeps visitors on the page and shows the result in place.

Can I send visitors back to my own thank-you page?

Yes, on Pro. Set a redirect URL in the form's settings, or add a hidden _redirect field with the full URL of your Astro thank-you page.

Does the script work with View Transitions?

Yes, if it runs on every navigation. With Astro's ClientRouter, put the setup inside a listener for the astro:page-load event so the form is wired up again after each page swap.

Can I use a React, Vue or Svelte component instead?

Yes. Add the framework's integration and render the component as an island with client:load. The React, Vue and Svelte versions post to the same endpoint.

Start collecting form submissions

Set up your forms with built-in spam protection in minutes for free.

Jane Smith

jane@smith.design

Live

12.4k

Submissions

24

Forms

0

Spam