Contact form for Astro websites
Astro's zero-JS-by-default philosophy pairs perfectly with the splitforms endpoint. Use a pure HTML form for static pages, an Astro Action for SSR, or a React/Vue/Svelte island for client-side interactivity. Same backend, three patterns, zero hydration cost when you don't need it.
What your Astro contact form actually looks like.
Drop-in form backend with spam filtering, signed webhooks, and a real submissions dashboard. The same code in this preview is what you copy into your Astro project — no SDK, no plugin, no PHP.
- ✓1,000 submissions per month, free forever
- ✓Honeypot + AI spam classifier on every plan
- ✓Signed webhooks to Slack, Discord, your server
Ship a Astro contact form without a backend.
No SDK, no PHP, no plugin. Your form posts standard FormData to one URL — submissions land in your inbox.
Get your free access key
Verify your email and your access key is generated instantly. Free for 1,000 submissions per month, forever.
By signing up, you agree to our terms and privacy policy.
Drop in the Astro code
Copy the Astro snippet on the right and paste it into your project. Replace YOUR_ACCESS_KEY with the key from step 1.
Submissions land in your inbox
Hits your dashboard and email in seconds. Forward to Slack, Discord, Sheets, Notion, or any signed webhook URL.
Try it now — no signup, no key.
This is a styled HTML preview of what your Astro form will look like. Submitting opens a confirmation, no real request is sent.
Your Astro form posts FormData to /api/submit. Splitforms validates the access key, runs the spam classifier, and forwards the parsed submission to your inbox plus the dashboard.
- →14ms median round-trip from the edge.
- →Honeypot + classifier, no CAPTCHA.
- →Per-domain key locking out of the box.
{
"access_key": "sk_live_4f9a_••••",
"name": "Maya Iyer",
"email": "maya@studio71.co",
"message": "…"
}How to ship this without regrets.
Five rules that make the difference between a form that works in the demo and a form that survives launch traffic.
- 01
Use the pure HTML form pattern (the snippet above) for static pages — zero JS shipped, no hydration cost, works with JavaScript disabled.
- 02
Set `PUBLIC_SPLITFORMS_KEY` in `.env` and reference it in the Astro frontmatter. Lock the key to your domain in the splitforms dashboard so a leaked key can't be replayed.
- 03
Add `<input type="hidden" name="redirect" value={new URL('/thanks', Astro.site).href} />` so non-JS users still land on a thank-you page after submitting.
- 04
If you need client-side success/error UI, hydrate a single small island (Preact is 3kb) rather than React (45kb). The form component is tiny.
- 05
Cache the contact page aggressively — set `Cache-Control: public, max-age=3600` in your adapter config. The form action is server-side regardless.
What bites people who skip the docs.
Worth a 60-second skim before you ship to production. Each one has caused a Astro support ticket at least once.
PUBLIC_ prefix is required for client-exposed env vars
Astro mirrors Vite's env-var convention: only variables prefixed with PUBLIC_ are exposed to client-side code (and to .astro files when output: 'static'). If you write import.meta.env.SPLITFORMS_KEY, you'll get undefined at build time. Rename to PUBLIC_SPLITFORMS_KEY.
client:load on the form island defeats the point of Astro
If you wrap the form in <MyForm client:load />, you ship a full React/Preact runtime just for one form. Use client:visible (load when scrolled into view) or client:idle (load after main thread is free) instead. For zero-JS forms, skip the island entirely and use a plain HTML form action.
Astro Actions need a try/catch or they crash the page
If your action throws, Astro 5's behavior is to render an error page rather than return the error to your form. Wrap the splitforms fetch in try/catch and return { success: false, message } from the action — your form component can then render the message.
View Transitions can break form re-submit state
If you've enabled <ViewTransitions /> in your layout, navigating to /contact and back may re-mount the form mid-submission. Add data-astro-reload to the form's submit anchor or guard with if (status === 'loading') return at the top of your handler.
Adapter mismatch: form posts to /api/submit fail in static mode
If output: 'static' in astro.config.mjs, you can't have a server endpoint at all — your form must POST directly to splitforms.com. That's the recommended setup. Switch to output: 'server' or 'hybrid' only if you genuinely need a server-side proxy.
Frontmatter fence forgotten — form action renders as literal text
Astro requires the --- fence at the top of .astro files for any frontmatter (imports, Astro.props, env reads). If you write const accessKey = import.meta.env.PUBLIC_SPLITFORMS_KEY; without the surrounding fence, Astro treats the line as plain text in the rendered HTML — you'll see the JavaScript leak into the page above your form. The build doesn't error; it just renders nonsense. Always wrap any logic in --- and reference values via {accessKey} in the template body.
How Astro handles forms without splitforms.
The shape of the problem before splitforms enters the picture — and the gap it fills for Astro specifically.
Astro's whole pitch is shipping zero JavaScript by default. A native contact form on Astro means either (a) building a form with no submission target — useless — or (b) standing up an API endpoint via output: 'server' or 'hybrid', which means writing a Node/Bun/Deno handler, picking an email provider, writing your own honeypot logic. Astro 5 added typed Actions, but they're a wrapper around the same underlying fetch — you still deliver the email yourself. The result: every Astro contact-form tutorial ends with 'now configure SendGrid'. Splitforms is the SendGrid-replacement that doesn't require an account, an API key for the email provider, or DNS records for SPF/DKIM.
Two ways to ship splitforms on Astro.
Pick the pattern that matches your constraints — JS budget, key-exposure tolerance, server-side opacity. Both produce the same result.
Pattern A — pure HTML form (zero JS shipped)
The Astro-native approach: a .astro file with frontmatter pulling the key from import.meta.env, then a static <form action> that posts directly. Zero hydration, zero island, zero KB JavaScript.
Pattern B — Astro Action for typed server-side proxying
Astro 5 Actions give you Zod-validated, type-safe form handlers. Use one to keep the access key off the client entirely — the form posts to the action, the action proxies to splitforms.
Shipping Astro + splitforms to production.
Host-specific gotchas, env-var conventions, and the boring-but-load-bearing details for putting this on the public internet.
Astro deploys cleanly to Vercel, Netlify, Cloudflare Pages, GitHub Pages, and any static host with output: 'static'. For Astro Actions (Pattern B), you need output: 'server' or 'hybrid' and a matching adapter (@astrojs/vercel, @astrojs/netlify, @astrojs/cloudflare). On Cloudflare Pages with the Cloudflare adapter, the Action runs in a Worker — keep the splitforms fetch tight (no extra proxying) to stay under the 10ms CPU budget on the free plan. The PUBLIC_ prefix is mandatory for env vars exposed to client-rendered .astro files; vars without it are silently undefined. Lock the key to your *.pages.dev and custom domain.
splitforms vs native astro.
What you get for free vs what you build, pay for, or do without.
Astro Actions variant (server-side, hides the access key)
Astro 5+ ships a typed Actions API. Use it to keep your splitforms key entirely server-side, with full TypeScript inference on your form fields.
Things developers ask before they integrate.
Direct answers, no marketing fluff. Missing one? Email hello@splitforms.com.
Ship your Astro contact form in 60 seconds.
1,000 free submissions per month. No credit card. Lock the access key to your domains, paste the snippet, watch submissions land in your inbox.
