splitforms.com

Contact form · Svelte

Contact form for Svelte websites

SvelteKit, classic Svelte, Vite, Astro islands — pick your flavor. One reactive component, full form handling, no backend route. Works with Svelte 4 stores and Svelte 5 runes.

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

No backend code

No server, API route, or SDK. Your Svelte 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 Svelte markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.

Copy-paste ready

Your Svelte 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
<script>
  let status = "idle";

  async function onSubmit(e) {
    status = "loading";
    const formData = new FormData(e.target);
    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.target.reset();
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="name"  required />
  <input name="email" type="email" required />
  <textarea name="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!</p>{/if}
  {#if status === "err"}<p>Error.</p>{/if}
</form>

How to add it

How to add a contact form to a Svelte website.

To add a contact form to a Svelte 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 Svelte snippet into your project

Copy the Svelte 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 Svelte 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
Svelte contact form submissions in the splitforms dashboard

No backend needed

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

Your Svelte 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 in SvelteKit, classic Svelte, and Svelte islands in Astro
  • Reactive state with `let` (Svelte 4) or `$state` (Svelte 5)
  • Single `.svelte` file — copy, paste, ship
How splitforms processes a Svelte form submission

Best practices

What a production-ready Svelte 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.

  • Pick one syntax era: all Svelte 4 OR all Svelte 5 in the same component. Don't mix let reactivity with $state — it works but is confusing for the next person to touch the file.
  • Use bind:this={formEl} to get a stable ref to the form element, then call formEl.reset() after a successful submit instead of relying on e.target.reset() (which can be null after re-renders).
  • Set the access key via VITE_SPLITFORMS_KEY (Vite) or PUBLIC_SPLITFORMS_KEY (SvelteKit). Lock the key to your domain in the splitforms dashboard.
Production-ready Svelte 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

Svelte contact form questions.

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

Paste the .svelte snippet above into any component file (e.g. src/lib/ContactForm.svelte), import it where you need it, and replace YOUR_ACCESS_KEY. That's it.

Does splitforms work with SvelteKit SSR / SSG?

Yes — both. The component is client-only because it uses fetch, but it hydrates inside an SSR-rendered page without issue. For server-side submission with progressive enhancement, see the dedicated /forms/sveltekit page.

How do I handle form errors in Svelte?

Use a status variable with four states: idle, loading, ok, err. Show inline messages with {#if} blocks. The fetch returns { success, message? } — render data.message for the user when success is false.

Can I use splitforms with SvelteKit form actions instead?

Yes — that's the SvelteKit-specific pattern (server-side, progressive enhancement, no client JS required). It's documented on the /forms/sveltekit page.

How do I customize the success / redirect behavior?

Two options. Stay on-page with a Svelte-rendered success message (default in our snippet), or configure a thank-you URL in Dashboard → Form settings → Redirect and use a native form submit.

Does this work with Svelte 4 and Svelte 5?

Yes. The hero snippet uses Svelte 4 syntax (works in 5 too with a deprecation warning); the alternative-pattern snippet uses Svelte 5 runes. Pick whichever matches your project.

Svelte 5 runes vs Svelte 4 reactive let — pick one

Svelte 5 introduces $state(...) runes; Svelte 4 uses plain let status = 'idle'. They're not interchangeable in the same component. Check package.json for "svelte": "^5" and use runes accordingly. Mixing them throws a confusing 'rune used outside .svelte.js' error.

on:submit|preventDefault becomes onsubmit={(e) => …} in Svelte 5

If you copy a Svelte 4 snippet into a Svelte 5 project, the on:submit|preventDefault modifier syntax is gone. Use onsubmit={(e) => { e.preventDefault(); … }} or migrate to a SvelteKit form action that handles preventDefault for you.

bind:value desyncs FormData if you forget the `name` attribute

FormData reads from name="…" attributes, not Svelte's bind:value. If you bind a value but skip the name attribute, the field is silently dropped from the POST body. Always set both.

Vite-only Svelte projects can't use $env/static/private

$env/static/private is a SvelteKit feature, not a Vite-Svelte one. In a vanilla Vite + Svelte project, use import.meta.env.VITE_SPLITFORMS_KEY (must have the VITE_ prefix or it's undefined client-side).

Component re-mount on hot-reload resets your status state

During development, saving a .svelte file may re-mount the component mid-submission. The fetch keeps running but the UI loses its 'loading' state. Not a real bug — just don't panic when you see it in dev. Production behaves correctly.

Reactive `$:` block fires before the value you depend on is assigned

If you write $: if (status === 'ok') resetForm(); and status = 'ok' inside an async fetch handler, the block runs synchronously after each top-level update — but resetForm() may execute before the DOM has flushed the disabled state on the submit button, causing a visible flicker where the button briefly re-enables and then the form clears. Either move the reset inside the handler after await tick(), or use Svelte 5's $effect rune which schedules properly relative to renders. Svelte 4's $: is synchronous and easy to misuse for side effects.

How does Svelte handle forms without splitforms?

Plain Svelte (without SvelteKit) is a compiler — there's no runtime route handler, no server, no built-in form delivery. To ship a working form natively you'd add a separate Node/Bun/Express layer, write the SMTP wiring, and operate it. SvelteKit ships form actions and use:enhance for progressive enhancement, but those just give you ergonomic ways to call your own backend; the actual email-delivery, spam-filtering, and submission-storage are still on you. Svelte 5 runes change reactivity syntax, not the operational model. Splitforms removes the entire 'add a backend' step: the runtime is one URL, hosted by us.

Any deployment notes for shipping Svelte to production?

Vite + Svelte (non-Kit) builds a static bundle for any host. SvelteKit deploys via adapters: @sveltejs/adapter-vercel, -netlify, -cloudflare, -node, -static. The form posts client-side regardless of adapter, so the form itself works identically on each. Use VITE_SPLITFORMS_KEY for plain Vite-Svelte projects; SvelteKit uses $env/static/public from PUBLIC_SPLITFORMS_KEY. Svelte islands embedded in Astro hydrate via client:visible — the form's fetch only runs after the user scrolls to it, saving JS execution on initial load. Lock the access key to your domain.

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