Astro ships almost no JavaScript by default. That is great for speed and worse for the old habit of bolting on a Node mailer or an SSR adapter just so a contact form can POST. This guide shows how to add a contact form to an Astro site with a static form API: plain HTML in a .astro component, no adapter required, and optional client JS only if you want inline success states.
The same pattern works on Cloudflare Pages, Netlify, Vercel, GitHub Pages, and any host that serves dist/.
What you’ll need
- An Astro project (
astro buildstatic output is enough) - Ability to add a component or page with a form
- A free FormsReach API key
No SSR adapter for the form. No mailto:. Optional WhatsApp later on Pro or Agency.
Why Astro forms do not need a server
| Approach | Problem |
|---|---|
| SSR adapter + server endpoint | Extra deploy surface for one form |
mailto: |
Opens the visitor’s mail client; weak spam control |
| Client-only secrets | Never put private mail-provider tokens in the browser |
| Form API | Browser POSTs to a managed URL; site stays static |
FormsReach is a form API: set action to https://api.formsreach.com/submit, include a hidden api_key, and notifications leave your app. Pillar guide: send form submissions to WhatsApp. Broader static-site notes: contact form on a static website without a backend.
Step 1: Create a ContactForm component
Prefer a small .astro component so every page reuses the same markup. Zero client JS:
---
// src/components/ContactForm.astro
---
<form action="https://api.formsreach.com/submit" method="POST">
<input type="hidden" name="api_key" value="YOUR_API_KEY">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Submit Form</button>
</form>
Replace YOUR_API_KEY with the key from the dashboard. Keep field name attributes consistent with how you want notifications to read (name, email, message work out of the box).
Style with scoped CSS, Tailwind, or global classes. Astro does not need a framework island for this form.
Step 2: Use the component on a page
---
import ContactForm from '../components/ContactForm.astro';
---
<main>
<h1>Contact</h1>
<ContactForm />
</main>
Build and deploy as usual (astro build, then host dist/). No form-specific adapter.
Step 3: Domain allowlist and thank-you page
- Publish the site on HTTPS.
- In FormsReach, add the live host under Allowed domains.
- Optionally set a redirect URL to a static
/thanks/page in the same Astro project.
Test on production, not only astro dev, so the allowlist matches the real origin.
Step 4: Env-friendly API key (optional)
For open-source repos, inject the key at build time instead of committing it:
---
// src/components/ContactForm.astro
const apiKey = import.meta.env.PUBLIC_FORMSREACH_KEY;
---
<form action="https://api.formsreach.com/submit" method="POST">
<input type="hidden" name="api_key" value={apiKey}>
<!-- fields unchanged -->
</form>
Set PUBLIC_FORMSREACH_KEY in your host’s env, and keep domain allowlisting enabled. The form key is public by design for HTML embeds; the allowlist is the main abuse control.
Step 5: Optional JS for inline success
If you want fetch submit without a full navigation, use the browser SDK on a page that opts into a script:
<script src="https://unpkg.com/@formsreach/js/dist/formsreach.min.js"></script>
<script>
FormsReach.init({ apiKey: 'YOUR_API_KEY' });
</script>
<form data-formsreach>
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Submit</button>
</form>
Load that only where you need it so the rest of the site stays zero-JS. React islands can use @formsreach/react instead: React contact form without a backend. For App Router Next, see Next.js contact form without API routes.
Step 6: Notifications (email first, WhatsApp when ready)
Free includes 500 credits/month. A typical path is 1 credit for an accepted submission and 1 credit for an email notification. Webhooks are 0 credits on every plan.
WhatsApp via the official Meta WhatsApp Business API is on Pro and Agency. Turn it on in the dashboard without changing your Astro markup.
Troubleshooting
Form reloads, nothing in the dashboard
Confirm action and method="POST", and that api_key is present in the built HTML. Check Network for api.formsreach.com.
Works locally, fails in production
Add the published host to Allowed domains. Confirm env-based keys were inlined at build time.
I added an adapter only for the form
You can remove it for this path. The form does not need SSR.
Want multi-framework snippets
Enable code tabs on this post layout or copy from the homepage CodeTabs; the HTML/Astro path above is the zero-JS default.
Keep Astro static; let a form API own delivery
An Astro contact form does not need a server of your own. One POST endpoint, spam filtering, email on Free (500 credits/month), free webhooks, and WhatsApp on Pro and Agency via Meta’s Business API. That preserves Astro’s static default while still shipping a real contact path.
