Contact form for Gatsby websites
Gatsby is React under the hood, so any React form works — but the build is static. Use the splitforms endpoint to skip Netlify Forms (which limit submissions on free tier), Lambdas, Gatsby Functions, and AWS API Gateway entirely. One React component, 1,000 free submissions per month, dashboard included.
What your Gatsby 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 Gatsby 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 Gatsby 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 Gatsby code
Copy the Gatsby 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 Gatsby form will look like. Submitting opens a confirmation, no real request is sent.
Your Gatsby 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 `GATSBY_SPLITFORMS_KEY` in your `.env.production` and `.env.development` files. Gatsby reads them at build time and inlines the value into the static JS bundle — that's expected, lock the key to your domain.
- 02
Don't wrap the form in `<Link>` or any component that intercepts navigation. Use a normal `<form>` element with onSubmit.
- 03
If you have multiple forms (contact, demo, newsletter), include a `form-name` hidden field per form so the splitforms dashboard groups them.
- 04
Build the /thanks page as a real Gatsby page (`src/pages/thanks.js`). The redirect from splitforms uses the URL — Gatsby serves it as static HTML.
- 05
For Gatsby v5 partial hydration, mark the form file with `'use client'` so React knows to hydrate the onSubmit handler on the client.
What bites people who skip the docs.
Worth a 60-second skim before you ship to production. Each one has caused a Gatsby support ticket at least once.
GATSBY_ prefix required for env vars exposed at build time
Gatsby's webpack config only exposes process.env.* variables prefixed with GATSBY_. If you write process.env.SPLITFORMS_KEY, you'll get undefined in the browser bundle. Rename to GATSBY_SPLITFORMS_KEY — and accept that it's bundled into the static JS (lock the key to your domain in the splitforms dashboard).
Gatsby's <Link> can't wrap a form's submit handler
Gatsby's <Link> component prevents default navigation. If you wrap your form in <Link to="/thanks"> thinking the redirect will fire, it won't — the form's submit event runs, but the navigation is suppressed. Use a hidden redirect input on the form and let splitforms handle the 302.
SSR + client hydration mismatch on form initial state
If you use useState('idle') in your form and render any state-dependent UI on first paint, Gatsby's static HTML and React's client render can diverge — you'll see a hydration warning. Render the form unconditionally; only render status messages inside the handler-triggered branches.
Gatsby v5 partial hydration changed how forms hydrate
Gatsby 5 introduced partial hydration via React Server Components. If your form is in a Server Component, the onSubmit handler won't bind. Add 'use client' at the top of the file (or use a separate ContactForm.client.jsx).
@reach/router (legacy) intercepts form submits with onSubmit handlers
Older Gatsby setups used @reach/router, which has a known issue where it sometimes intercepts form submissions. If you see your form not POSTing, ensure you've migrated to Gatsby v4+ (uses its own router) or wrap the form in a normal <div> not <Router>.
gatsby-plugin-offline caches the contact page and serves a stale form
If your site uses gatsby-plugin-offline (or the older gatsby-plugin-manifest with service worker registration), the contact page HTML is cached aggressively. After you rotate your GATSBY_SPLITFORMS_KEY and redeploy, returning visitors keep submitting against the old key from the cached bundle until the service worker fetches a fresh shell — sometimes days. Fix: bump the SW cache version in gatsby-config.js on key rotation, or skip the offline plugin on the contact route via runtimeCaching rules. Alternatively, switch to gatsby-plugin-pwa which handles versioned cache invalidation per-page.
How Gatsby handles forms without splitforms.
The shape of the problem before splitforms enters the picture — and the gap it fills for Gatsby specifically.
Gatsby builds a static React app, so 'native' means choosing between (a) Netlify Forms (Gatsby-on-Netlify only, 100 free submissions/month, gatsby-plugin-netlify required), (b) Gatsby Functions (deprecated in Gatsby 5 — they were removed when Gatsby Cloud shut down), or (c) a third-party form backend. Gatsby v4 had Functions running as Lambda-equivalent serverless routes; v5 removed them entirely. Result: every Gatsby contact form today uses an external service. Splitforms is a drop-in replacement — same shape as Netlify Forms (POST to a URL), same shape as Formspree, but with 5× the free monthly submissions and built-in spam filtering.
Two ways to ship splitforms on Gatsby.
Pick the pattern that matches your constraints — JS budget, key-exposure tolerance, server-side opacity. Both produce the same result.
Pattern A — React component (Gatsby v4/v5)
Standard React function component, useState for status. Drop into src/components/ContactForm.jsx and import on any page. Set GATSBY_SPLITFORMS_KEY in .env.production and .env.development.
Pattern B — partial hydration (Gatsby v5)
Gatsby 5's partial hydration mounts only the components marked client-bound. Add 'use client' at the top of the form file so the onSubmit handler binds at hydration time; the rest of the page stays static.
Shipping Gatsby + splitforms to production.
Host-specific gotchas, env-var conventions, and the boring-but-load-bearing details for putting this on the public internet.
Gatsby builds static HTML + JS that deploys to any host: Netlify, Vercel, Cloudflare Pages, AWS Amplify, S3 + CloudFront, Gatsby Cloud (sunset 2024 — migrate). The form posts cross-origin to splitforms regardless of host. Env vars exposed to the browser bundle must be prefixed GATSBY_ — anything else is undefined client-side. The key gets bundled into the JS at build time; lock it to your domain in the splitforms dashboard. For headless Gatsby + WordPress / Contentful setups, the form lives in the React tree, not the CMS — no special CMS wiring required.
splitforms vs native gatsby.
What you get for free vs what you build, pay for, or do without.
Things developers ask before they integrate.
Direct answers, no marketing fluff. Missing one? Email hello@splitforms.com.
Ship your Gatsby 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.
