FormLoom
A

Astro contact form

Verified against Astro 5.x · June 2026

Astro ships zero JS by default, and your contact form shouldn't break that. With FormLoom you write a normal `<form action method="POST">` in an `.astro` page and it just works on a fully static build — no API routes, no server adapter, no client island. If you want AJAX with a success state, add a tiny `<script>` island; otherwise the plain form is the whole integration.

The code

Drop a `<form action="https://api.formloom.ai/submit/KEY" method="POST">` into any `.astro` page. For an inline success message without a full page reload, progressively enhance with a small client-side script.

Astro page (static)
src/pages/contact.astro
---
// src/pages/contact.astro — static, no server needed
---
<form action="https://formloom.vercel.app/api/submit/YOUR_ACCESS_KEY" method="POST">
  <label>
    Name
    <input type="text" name="name" required />
  </label>
  <label>
    Email
    <input type="email" name="email" required />
  </label>
  <label>
    Message
    <textarea name="message" required placeholder="How can we help?"></textarea>
  </label>
  <!-- honeypot: bots fill this, humans don't see it -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />
  <button type="submit">Send</button>
</form>
AJAX fetch (vanilla JS)
contact.html
<form id="contact-form">
  <label>
    Name
    <input type="text" name="name" required />
  </label>
  <label>
    Email
    <input type="email" name="email" required />
  </label>
  <label>
    Message
    <textarea name="message" required placeholder="How can we help?"></textarea>
  </label>
  <!-- honeypot: bots fill this, humans don't see it -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />
  <button type="submit">Send</button>
</form>

<script>
const form = document.getElementById("contact-form");
form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const res = await fetch("https://formloom.vercel.app/api/submit/YOUR_ACCESS_KEY", {
    method: "POST",
    headers: { "Content-Type": "application/json", Accept: "application/json" },
    body: JSON.stringify(Object.fromEntries(new FormData(form))),
  });
  const data = await res.json();
  if (data.success) {
    form.reset();
    alert("Thanks — we got your message.");
  } else {
    alert(data.message || "Something went wrong.");
  }
});
</script>
@formloom/client (typed SDK)
submit.ts
// npm i @formloom/client
import { createForm } from "@formloom/client";

type Contact = {
  name: string;
  email: string;
  message: string;
};

const form = createForm<Contact>("YOUR_ACCESS_KEY");

// End-to-end typed: TS errors if you send the wrong shape.
const { success, message } = await form.submit({
  name: form.name.value,
  email: form.email.value,
  message: form.message.value,
});

Gotchas

  • On a static Astro build there is no server — so use the plain HTML form (browser POSTs directly to FormLoom) rather than an Astro endpoint.
  • If you use Astro's hybrid/server output and want server-side handling, create an API route that forwards to FormLoom — but for most static sites you don't need one.
  • Add a hidden `redirect` field to send users to a thank-you page after submit.

Form types for Astro

Tailored field sets and code for common use cases.

FAQ

No. The plain HTML form POSTs directly to FormLoom from the browser, so it works on a fully static build with no adapter.
Astro contact form — copy-paste code (verified 5.x) · FormLoom