Ship a contact form on Astro before your coffee gets cold

April 3, 2026 · 6 min read

Warm cafe lights behind a laptop — shipping a contact form fast

Astro is my favourite way to build a small site in 2026. It's fast, it's boring in the ways that matter, and it stays out of your way. The one thing it pointedly does not do is give you a contact form. You have to bring your own.

This post is me bringing my own, with a stopwatch, on a Wednesday morning, between the first and second sip of coffee. If you read fast and already have an Astro project open, you'll be done before I am.

Here's the deal I'm making with you: no serverless function, no @astrojs/node, no third-party React component, no "first install these eleven packages." Just plain HTML posting to a hosted endpoint. That's it.

Timer starts.

Minute 1 — Grab an endpoint

Sign up somewhere that gives you a form URL. I'm going to use FormTo because I work here and I know the URL shape, but the pattern is the same with any hosted form backend. You want something that looks like:

https://api.formto.dev/f/your-form-slug

Copy it. That's the whole first minute. Go pour the coffee.

Minute 2 — Drop in the form

In your Astro project, open whatever page you want the form on. Let's say src/pages/contact.astro. Paste this:

---
const endpoint = 'https://api.formto.dev/f/your-form-slug'
---

<html lang="en">
  <head>
    <title>Contact us</title>
  </head>
  <body>
    <main class="mx-auto max-w-lg p-8">
      <h1 class="text-3xl font-bold mb-6">Say hi</h1>

      <form method="POST" action={endpoint} class="space-y-4">
        <label class="block">
          <span class="text-sm font-medium">Your name</span>
          <input type="text" name="name" required class="mt-1 w-full border rounded px-3 py-2" />
        </label>

        <label class="block">
          <span class="text-sm font-medium">Email</span>
          <input type="email" name="email" required class="mt-1 w-full border rounded px-3 py-2" />
        </label>

        <label class="block">
          <span class="text-sm font-medium">Message</span>
          <textarea name="message" rows="5" required class="mt-1 w-full border rounded px-3 py-2"></textarea>
        </label>

        {/* honeypot — humans never fill this */}
        <input type="text" name="website" tabindex="-1" autocomplete="off" class="hidden" aria-hidden="true" />

        <button type="submit" class="px-4 py-2 bg-black text-white rounded">
          Send
        </button>
      </form>
    </main>
  </body>
</html>

Two things to notice. The action attribute points at your hosted endpoint — that's the whole trick. And there's a hidden website input at the bottom. That's the honeypot. If a bot fills it, the submission gets dropped server-side. You don't have to do anything else with it. Leave it alone.

Minute 3 — The thank-you redirect

You probably want to send people somewhere friendly after they submit. Make a new file at src/pages/thanks.astro:

<html lang="en">
  <head><title>Thanks</title></head>
  <body>
    <main class="mx-auto max-w-lg p-8 text-center">
      <h1 class="text-3xl font-bold mb-2">Got it.</h1>
      <p class="text-gray-600">We'll get back to you within a day or two.</p>
      <a href="/" class="text-blue-600 underline">← Back home</a>
    </main>
  </body>
</html>

Now in the FormTo dashboard (or whichever backend you picked), set the "redirect after submit" to https://yoursite.com/thanks. Save. Done.

If you don't want a dashboard click, you can also add a hidden _redirect input to the form:

<input type="hidden" name="_redirect" value="https://yoursite.com/thanks" />

Either works. The dashboard option survives page refactors better. The hidden input is nice when you want per-form redirects without touching settings.

Minute 4 — Email and Slack (optional but feels magical)

This is the part where people always go "oh."

In the dashboard, under the form's notification settings, add your email. Every submission now arrives in your inbox formatted like a real message, not a blob of JSON. If you want to customize the template, you can — the editor takes about twenty seconds to figure out.

If you want it in Slack instead, grab a Slack incoming webhook URL and paste it into the "webhook" field. The payload goes straight into your channel. No middleware, no Zapier step, no "connect your account" dance.

That's four minutes. Your contact form is live, spam-protected by the honeypot, sending email and pinging Slack, and you wrote zero lines of backend code.

A few things I learned the hard way

A short list, from the time before I knew better.

Don't put sensitive things in hidden inputs. "Public endpoint" means public. If you need to tag a form with a secret, do it on your server before you ever render the HTML.

Don't disable native HTML validation because you read a React post about it. required, type="email", and friends do real work for free. Server-side validation is still your job, but belt-and-suspenders is the right default for forms.

Do use a name that matches what you want to see in exports. Past me called fields field_1, field_2. Future me, three months later, had to remember which one was the phone number. Don't be past me.

Do test the honeypot. Write a script that POSTs with the hidden field filled. Confirm the submission doesn't appear. Takes thirty seconds. Saves a week of confused "why is my spam filter not working" later.

Why Astro is a good fit for this

A lot of form tutorials lean on SSR or an API route because the author assumes you'll want custom validation, rate limiting, and a CRM integration wired up from day one. For most sites, you won't — and if you do, you'll want it handled by a tool that specializes in it, not a 40-line route file you'll regret.

Astro's sweet spot is static pages with small sprinkles of interactivity. A plain HTML form posting to a hosted endpoint fits that model perfectly. No island, no hydration, no server bundle, no cold starts. The page stays as fast as the rest of your Astro site, because the form is just HTML.


If you want to copy the exact setup above, create a free form — 25 submissions a month, no card, no trial countdown. Paste the URL into the action, deploy, and go pour a second coffee.

And if you're on Next, Framer, Webflow, Hugo, or 11ty instead of Astro — the same four minutes work. Only the file extension changes.

← All posts