Get your free splitforms access key
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Contact form · SvelteKit
Use SvelteKit's progressive-enhancement form actions for a no-JS-required experience, or a plain client-side fetch for tight control over loading state. Both patterns work with the splitforms endpoint — standard FormData in, JSON out, every adapter supported.

No server, API route, or SDK. Your SvelteKit form posts straight to one endpoint.
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.
It's your own SvelteKit markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.
Copy-paste ready
Replace YOUR_ACCESS_KEY with the key from your dashboard — that's the whole integration. No SDK to install, no build step, just the svelte you already write.
<!-- src/routes/contact/+page.svelte -->
<script>
let status = "idle";
async function onSubmit(e) {
status = "loading";
const formData = new FormData(e.currentTarget);
formData.append("access_key", "YOUR_ACCESS_KEY");
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
body: formData,
});
const data = await res.json();
status = data.success ? "ok" : "err";
if (data.success) e.currentTarget.reset();
}
</script>
<form on:submit|preventDefault={onSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" />
<button disabled={status === "loading"}>
{status === "loading" ? "Sending…" : "Send"}
</button>
{#if status === "ok"}<p>Thanks! We'll be in touch.</p>{/if}
{#if status === "err"}<p>Something went wrong. Try again?</p>{/if}
</form>How to add it
To add a contact form to a SvelteKit website you need three things: a free splitforms access key, the svelte snippet above, and your key pasted into it. No backend, server, or SDK — the form posts to one URL and every submission lands in your inbox and dashboard.
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Copy the SvelteKit code example into your project and replace YOUR_ACCESS_KEY with the key from step 1.
Submissions arrive in the splitforms dashboard within seconds. Free includes inbox delivery; Pro adds Slack, Discord, Sheets, or any signed webhook URL.
Where submissions go
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it ever reaches you. Search, export to CSV, or forward it to a webhook or Slack on Pro.

No backend needed
Your SvelteKit form posts standard FormData to one URL. Splitforms validates the access key, runs the spam classifier, and forwards it to your email — so there's no server, API route, or database for you to build or maintain.

Best practices
The difference between a form that works in the demo and one that survives launch traffic — the production-tested defaults, in priority order.

How SplitForms works
Connect your form, collect every submission, and send data where it needs to go — without building backend infrastructure.

Point your form to your unique SplitForms endpoint. That's it.

We instantly capture and organize every submission in your inbox.

Send data to email, spreadsheets, CRMs, webhooks, and 7,000+ apps.

No credit card required. Set up in under 60 seconds.
Connect & automate
SplitForms works with the destinations you route to and the platforms you build on — from Slack and Sheets to WordPress, Shopify, and Next.js.

Trusted by indie teams and agencies shipping forms worldwide
Testimonials
40 quotes on record — from indie hacks to agency migrations.
“I replaced a Lambda + DynamoDB + SES contact form with six lines of HTML. It took eleven minutes, and the dashboard is better than what I was going to build.”
“We migrated 14 client sites off Formspree in a single weekend. The price is a third of what we paid, the API is more honest, and the spam filter actually works.”
“The webhook payload is signed, idempotent, and well-shaped. It reads like code from a competent team, not a CRUD app held together with duct tape.”
“I stopped reaching for Typeform on small marketing sites. splitforms covers 90% of the use case at none of the bloat.”
“I onboarded our whole agency in an afternoon. The MCP integration meant Cursor literally dropped the form straight into our client repos for us.”
“The free plan gave me 500 submissions before I paid a cent, and Pro is five dollars a month. I've spent more on coffee deciding which backend to use.”
“Spam went from forty junk entries a day to zero, with no reCAPTCHA puzzle ruining the form. The honeypot and time-trap just quietly do their job.”
“Point the form action at one endpoint and you're done. No SDK, no client library, no build step. This is how a form backend should feel.”
“Leads land in Slack the second someone submits, and a copy goes to Google Sheets for the sales team. I wired both up in under ten minutes.”
“I run a static Hugo site on a five-dollar VPS. splitforms gave it a real contact form without me standing up a single server.”
Questions
Two paths. (1) Client-side fetch (snippet above): drop a .svelte file under src/routes/contact/+page.svelte, no server file needed. (2) Form action with progressive enhancement: see the alternative-pattern snippet — adds a +page.server.ts and uses use:enhance.
Yes. The form action just needs to POST FormData to splitforms.com from your +page.server.ts. The alternative-pattern snippet shows the full flow including error handling and redirect.
If using form actions, return fail(400, { message }) from the action — SvelteKit exposes it via the form prop. If using client fetch, render messages from data.message in your status state.
Yes — that's the recommended progressive-enhancement pattern. use:enhance with no arguments uses the default behavior: the form posts traditionally without JS, AJAXes with JS, and the page state updates without a full reload either way.
From a form action: throw redirect(303, '/thanks'). From client fetch: navigate with goto('/thanks'). Or skip both and render an inline success message inside {#if status === 'ok'}.
Yes. Vercel, Netlify, Node, Cloudflare Workers, Cloudflare Pages, static, deno — all of them. The form posts to splitforms.com from the browser, so the adapter only matters for SSR rendering of the form page itself.
If you use +page.server.ts form actions, the form's action attribute must be ?/contact (or whatever you named it) — not /api/contact. Forgetting the ?/ prefix routes to a 404 because SvelteKit doesn't recognize it as an action.
use:enhance without arguments uses default progressive-enhancement behavior — which calls the form action and re-renders. If you need custom logic (toast on error, etc.), return a function: use:enhance={({ formData, cancel }) => async ({ result }) => …}.
If you use a SvelteKit form action that proxies to splitforms.com via fetch, the round-trip eats your CF Worker time budget on cold start. Skip the proxy: have the form POST directly to splitforms.com from the client (the snippet above does this).
$env/static/public is inlined at build time (faster, but key is in the bundle). $env/dynamic/public is read at runtime (slower, but rotatable without rebuild). For the splitforms key, static is fine if you've locked the key to your domain.
If you try to read the splitforms response in +page.server.ts's load(), you'll find it's a separate request. Form action results are passed via the form prop on the page component, not via load. Read them as export let form.
If your +page.server.ts exports actions = { contact: async () => {…}, newsletter: async () => {…} } and your form posts to ?/contact, SvelteKit dispatches by the URL query — but the submitter button must also have formaction="?/contact" if you have multiple submit buttons in one form. Browsers send only the clicked submitter's formaction, so a stray button targeting ?/newsletter will hit the wrong action even when the form's main action is ?/contact. Symptom: submissions inexplicably trigger the wrong handler. Audit every <button type="submit"> for stale formaction attributes left over from layout iteration.
SvelteKit's headline form story is form actions — write a default-exported actions object in +page.server.ts, and SvelteKit handles FormData parsing, progressive enhancement (use:enhance), and result passing via the form prop. It's elegant DX — but it doesn't deliver email. You still write the SMTP integration, the spam-filter logic, the database for storing submissions, the webhook fan-out. The result is a form action that's ~80% boilerplate and ~20% your business logic. Splitforms collapses the boilerplate: the form posts directly from the browser to splitforms.com, the form action becomes a thin proxy (or you skip it entirely), and the operational layer disappears.
SvelteKit's adapter system is the deployment story: @sveltejs/adapter-vercel, -netlify, -cloudflare, -cloudflare-workers, -node, -static. The form's POST is cross-origin to splitforms regardless of adapter. On Cloudflare Pages/Workers (free tier: 10ms CPU per request), avoid Pattern B — the form action's fetch round-trip eats your budget; use Pattern A. On Vercel/Netlify, both patterns work with no measurable difference. $env/static/public inlines at build time (use for client-exposed keys); $env/static/private is server-only (use for Pattern B's server-action key). Domain-lock the access key.
Simple pricing
Choose a plan that fits your workflow — from a free form endpoint to full automations, exports, and higher submission limits.
Free forever
For side projects and indie devs.
For agencies and growing products.
Pay $59. 3 years sorted.
No credit card required on Free • Cancel anytime