splitforms.com

Contact form · SvelteKit

Contact form for SvelteKit apps

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.

  • 500 free / mo
  • 14ms latency
  • No backend code
SvelteKit contact form — copy-paste code for splitforms

No backend code

No server, API route, or SDK. Your SvelteKit form posts straight to one endpoint.

Straight to your inbox

Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.

Design without limits

It's your own SvelteKit markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.

Copy-paste ready

Your SvelteKit contact form, ready to paste.

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.

Generate access key
contact.svelte
<!-- 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

How to add a contact form to a SvelteKit website.

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.

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.

Drop the SvelteKit snippet into your project

Copy the SvelteKit code example into your project and replace YOUR_ACCESS_KEY with the key from step 1.

Receive submissions in your dashboard

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

Where do your SvelteKit form 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.

  • 500 submissions per month, free forever
  • Honeypot + AI spam classifier on every plan
  • Signed webhooks to Slack, Discord, your server
SvelteKit contact form submissions in the splitforms dashboard

No backend needed

Do you need a backend for a SvelteKit form? No.

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.

  • Works with SvelteKit form actions, `use:enhance`, or client-side fetch
  • Compatible with every adapter — Vercel, Netlify, Node, Cloudflare, static
  • Single `.svelte` route — no `+page.server.ts` required (but supported)
How splitforms processes a SvelteKit form submission

Best practices

What a production-ready SvelteKit form needs.

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

  • Default to the client-side fetch pattern (the snippet above) — fewer moving parts, works on every adapter without a server route.
  • If you need progressive enhancement (form works without JS), use form actions with use:enhance — the form posts traditionally on JS-disabled clients and AJAXes when JS loads.
  • Set the access key as PUBLIC_SPLITFORMS_KEY in .env — accessible from $env/static/public. Lock the key to your domain in the splitforms dashboard.
Production-ready SvelteKit contact form best practices

How SplitForms works

From form to workflow in 3 simple steps.

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

A SplitForms contact form submission launching straight to your inbox

Add your endpoint

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

HTML form pointing at a SplitForms submit endpoint

Receive submissions

We instantly capture and organize every submission in your inbox.

Submissions inbox with searchable leads and status pills

Route anywhere

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

Generic integration tiles for email, sheets, chat, CRM, automate, and webhook

No credit card required. Set up in under 60 seconds.

Connect & automate

Connect your favorite tools and automate everything

SplitForms works with the destinations you route to and the platforms you build on — from Slack and Sheets to WordPress, Shopify, and Next.js.

Connect your SplitForms form to Slack, Google Sheets, Mailchimp, Zapier and more

Trusted by indie teams and agencies shipping forms worldwide

PETAL/COKRAFT.DELINEAR-XBUILD.DEVSTUDIO 71MERIDIANFRAME&CO

Testimonials

Loved by developers shipping at every scale.

40 quotes on record — from indie hacks to agency migrations.

Questions

SvelteKit contact form questions.

View all FAQs
How do I add a contact form to SvelteKit?

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.

Does splitforms work with SvelteKit's form actions?

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.

How do I handle form errors in SvelteKit?

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.

Can I use splitforms with SvelteKit's `use:enhance` directive?

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.

How do I customize the success / redirect behavior?

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'}.

Does splitforms work with every SvelteKit adapter?

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.

Form actions need POST and the named action prefix

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 disables your client-side handler if you don't return a callback

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 }) => …}.

Cloudflare adapter has a 50ms cold start budget — fetch to splitforms eats it

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 vs $env/dynamic/public — pick the right one

$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.

load() functions can't access the form action result

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.

Named form actions need explicit `name` matching, not just URL hints

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.

How does SvelteKit handle forms without splitforms?

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.

Any deployment notes for shipping SvelteKit to production?

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

Start free. Scale when you need more.

Choose a plan that fits your workflow — from a free form endpoint to full automations, exports, and higher submission limits.

Free

$0

Free forever

 
Best for testing

For side projects and indie devs.

  • 500 submissions / mo
  • Unlimited forms
  • Email notifications included
  • Honeypot spam filtering
  • Submissions dashboard
  • MCP setup stays free
  • No credit card required

3-Year

$59/ 36 months
was $99 · save 40% · new-user price

Pay $59. 3 years sorted.

  • 15,000 submissions / mo
  • Unlimited forms
  • Everything in Pro
  • Renews every 3 years
  • Long-term discount
  • Priority support included
  • Vote on the roadmap

No credit card required on Free • Cancel anytime