Astro is a joy to build with, right up until you need a form. There's no server on the other end — so the default move is a hosted endpoint (Formspree, Netlify Forms, Web3Forms) that emails you the raw submission. Fine for one contact form. It falls over fast once you need several forms with real logic behind them.
Concrete case: a summer camp site. Registration, a medical form, a photo release, a liability waiver, maybe a deposit. Some fields only apply if an earlier answer was "yes." Files get uploaded. Branded confirmation emails go out. A mailto-style endpoint can't do any of that — and standing up five separate hosted forms, each with its own dashboard and submission cap, is its own headache.
The shape I landed on: keep Astro fully static, and point every form at one WordPress backend running CraftForms. WordPress does the things it's actually good at — storing, validating, routing, emailing. Astro keeps doing the thing it's good at — serving fast static pages.
How it wires together
Build the form once in WP with CraftForms. On the Astro side, a small component renders a placeholder <div>. At runtime, a tiny loader script (embed.js) fetches the live form definition from the backend and boots it in place. Submissions POST back to the backend, which validates, stores, and emails.
---
import CraftForm from "../components/CraftForm.astro";
---
<CraftForm embedKey="aHR0cHM6Ly9mb3Jtcy5leGFtcGxlLmNvbQ.abc123" />
Under the hood that's just:
<div data-craftforms-embed="…"></div>
<script is:inline defer src="https://forms.example.com/…/embed.js"></script>
is:inline matters here — it tells Astro not to try to bundle the external loader. Multiple forms on one page work fine; the loader upgrades every placeholder and only injects shared assets once.
Nothing about the form's fields, validation, or pricing lives in the Astro build. Change any of it in WordPress and it shows up with no rebuild.
Why it doesn't cost you the static-site speed
The page was already instant. Once the form boots, every interaction — conditional fields appearing, a live price recalculating as someone picks options — happens client-side, with zero backend round-trips. The backend is only touched once, on final submit. A parent can fill out a whole multi-field registration, toggling options and watching totals update, without the WordPress box doing anything until they hit submit.
Security, briefly
The submission endpoint is public by design, but not naive:
- The embed key is bound to the requesting domain; the backend checks
Originand rejects anything else. - Anything money- or capacity-related is recomputed server-side and rejected if it doesn't match — client-side numbers are never trusted.
- The WordPress install itself runs locked down: one plugin, no public pages, XML-RPC disabled, login hidden.
- Built-in spam heuristics run on every submission; a required-header check is available as an extra gate for high-traffic public forms.
I wrote up the full setup — the Astro component, the embed-key flow, the anti-abuse details, and the WordPress lock-down mu-plugin — on my blog:
👉 Add a Real Form Backend to Your Astro Site
Disclosure: I build CraftForms, the WordPress plugin used here — but the "static frontend, one locked-down WP backend" shape works with any plugin that exposes a REST endpoint and an embeddable form.
Top comments (0)