send.webforms.toRead the docs →

Frameworks and libraries

Add a contact form to Astro

Astro's whole point is shipping less JavaScript, so a contact form that drags in a client-side framework misses the plot. The version below ships zero bytes of JS: a plain HTML form posting to your endpoint, with a `_next` field to send the visitor to a thank-you page. If you do want an inline success state, there is a small island for it, and an Astro Actions example if you are running with an SSR adapter.

Get an endpointRead the docs

Setup

  1. Create a form, copy the endpoint

    One URL, nothing to install into your Astro project.

  2. Drop in the HTML

    A plain <form> in a .astro file works on a fully static build. Add _next to control the redirect after submit.

  3. Optionally enhance it

    Add the island below if you want to stay on the page and show an inline confirmation.

Astro examples

Swap in your own endpoint and these run as-is.

Zero JavaScript. Works on a fully static build served from any CDN.

src/pages/contact.astro
---
const ENDPOINT = "https://send.webforms.to/wft_your_key";
---

<form action={ENDPOINT} method="POST">
  <input name="name" placeholder="Your name" required />
  <input name="email" type="email" placeholder="you@example.com" required />
  <textarea name="message" placeholder="Message" required></textarea>

  <!-- where the browser goes after a successful submit -->
  <input type="hidden" name="_next" value="https://example.com/thanks" />

  <!-- honeypot -->
  <input name="_gotcha" tabindex="-1" aria-hidden="true"
         style="position:absolute;left:-9999px" />

  <button type="submit">Send</button>
</form>

Progressive enhancement with a small inline script - still no framework runtime.

src/components/ContactForm.astro
<form id="contact" action="https://send.webforms.to/wft_your_key" method="POST">
  <input name="name" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
  <p id="result" role="status" hidden></p>
</form>

<script>
  const form = document.getElementById("contact");
  const result = document.getElementById("result");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const res = await fetch(form.action, {
      method: "POST",
      headers: { accept: "application/json" },
      body: new FormData(form),
    });

    const data = await res.json();
    result.hidden = false;
    result.textContent = data.ok ? "Thanks, we got it." : data.message;
    if (data.ok) form.reset();
  });
</script>

Astro Actions, for projects running an SSR adapter.

src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro:schema";

export const server = {
  contact: defineAction({
    accept: "form",
    input: z.object({
      name: z.string(),
      email: z.string().email(),
      message: z.string(),
    }),
    handler: async (input) => {
      const res = await fetch("https://send.webforms.to/wft_your_key", {
        method: "POST",
        headers: {
          "content-type": "application/json",
          accept: "application/json",
        },
        body: JSON.stringify(input),
      });

      if (!res.ok) throw new Error("Submission failed");
      return { ok: true };
    },
  }),
};

Worth knowing

Astro form FAQ

Yes. A native HTML form POST needs no server at all. Add a _next field with your thank-you page URL and the browser is redirected there once the submission is accepted.

Ready to wire up your Astro form?

Free for 300 submissions a month. Endpoint in under a minute, no card required.

Start free