SvelteKit Contact Form - Add FormWit in 2 Minutes

Last updated: March 2026

Adding a SvelteKit contact form that sends emails usually means writing a server endpoint, configuring an email service, and handling validation on the backend. With a form backend, you skip all of that. This guide shows how to add a fully working contact form to any SvelteKit project in about 2 minutes.

How it works

Your Svelte component renders an HTML form. When a visitor submits it, the data is sent to a FormWit endpoint via a POST request. FormWit validates the submission, filters spam, stores it in your dashboard, and sends you an email notification. No SvelteKit server endpoints, no email setup, no database.

The flow:

  1. A visitor fills out the contact form in your SvelteKit app
  2. The component sends the form data to your FormWit endpoint
  3. FormWit validates the data, checks for spam, and stores the submission
  4. You receive an email notification and can view the submission in your dashboard

Set up a SvelteKit contact form

Step 1: Create a FormWit account

Go to app.formwit.com/auth/signup and create a free account. No credit card required. Click Create Form, name it (e.g., "Contact Form"), and copy the endpoint URL. It looks like https://app.formwit.com/api/s/YOUR_FORM_ID.

Step 2: Create the contact form component

Create a file at src/lib/components/ContactForm.svelte. This component uses Svelte's reactive declarations to track submission status and includes a honeypot field for spam protection.

<script>
  const FORM_URL = 'https://app.formwit.com/api/s/YOUR_FORM_ID';

  let status = $state('idle');

  async function handleSubmit(event) {
    status = 'sending';
    const form = event.currentTarget;

    try {
      const response = await fetch(FORM_URL, {
        method: 'POST',
        body: new FormData(form),
      });

      if (response.ok) {
        status = 'success';
        form.reset();
      } else {
        status = 'error';
      }
    } catch {
      status = 'error';
    }
  }
</script>

<form onsubmit|preventDefault={handleSubmit}>
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />
  </div>

  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>

  <div>
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>

  <!-- Honeypot spam protection -->
  <input
    type="text"
    name="_gotcha"
    style="display:none"
    tabindex="-1"
    autocomplete="off"
  />

  <button type="submit" disabled={status === 'sending'}>
    {status === 'sending' ? 'Sending...' : 'Send Message'}
  </button>

  {#if status === 'success'}
    <p class="success">Message sent successfully!</p>
  {/if}
  {#if status === 'error'}
    <p class="error">Something went wrong. Please try again.</p>
  {/if}
</form>

Replace YOUR_FORM_ID with the endpoint ID from your FormWit dashboard. The component uses FormData to serialize all named fields and sends them via fetch. The hidden _gotcha field is a honeypot: bots fill it in automatically, but real users never see it. FormWit uses this to filter spam submissions.

Step 3: Add the component to a page

Import the component into any +page.svelte file. For example, create a contact page at src/routes/contact/+page.svelte:

<script>
  import ContactForm from '$lib/components/ContactForm.svelte';
</script>

<svelte:head>
  <title>Contact Us</title>
  <meta name="description" content="Get in touch with us." />
</svelte:head>

<h1>Contact Us</h1>
<p>Have a question? Fill out the form below and we will get back to you.</p>

<ContactForm />

Step 4: Test it

Start your SvelteKit dev server:

npm run dev

Navigate to /contact, fill in the fields, and click "Send Message." The success message should appear without a page reload. Check your FormWit dashboard to see the submission and your email inbox for the notification.

Using SvelteKit form actions with FormWit

SvelteKit has a built-in form actions system that processes forms on the server. You can use this to proxy submissions through your own server before forwarding to FormWit. This is useful if you need server-side validation or want to keep your FormWit endpoint URL hidden from the client.

Create a +page.server.js file alongside your contact page:

// src/routes/contact/+page.server.js
import { fail } from '@sveltejs/kit';

const FORM_URL = 'https://app.formwit.com/api/s/YOUR_FORM_ID';

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();

    const name = data.get('name');
    const email = data.get('email');
    const message = data.get('message');

    // Server-side validation
    if (!name || !email || !message) {
      return fail(400, { error: 'All fields are required.' });
    }

    try {
      const response = await fetch(FORM_URL, {
        method: 'POST',
        body: data,
      });

      if (!response.ok) {
        return fail(500, { error: 'Submission failed. Please try again.' });
      }

      return { success: true };
    } catch {
      return fail(500, { error: 'Could not reach the form service.' });
    }
  },
};

Then update the page component to use SvelteKit's enhance directive for progressive enhancement:

<script>
  import { enhance } from '$app/forms';

  let { form } = $props();
</script>

<h1>Contact Us</h1>

<form method="POST" use:enhance>
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />
  </div>

  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>

  <div>
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="5" required></textarea>
  </div>

  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />

  <button type="submit">Send Message</button>

  {#if form?.success}
    <p class="success">Message sent successfully!</p>
  {/if}
  {#if form?.error}
    <p class="error">{form.error}</p>
  {/if}
</form>

With this approach, the form works even when JavaScript is disabled. SvelteKit processes the form on the server, forwards it to FormWit, and returns the result to the page. The use:enhance directive adds client-side interactivity (no full page reload) when JavaScript is available.

Adding a custom redirect

If you want to send visitors to a thank-you page after submission instead of showing an inline message, add a hidden redirect_to field:

<input type="hidden" name="redirect_to" value="https://yoursite.com/thank-you" />

This tells FormWit to redirect the browser after a successful submission. If you are handling the form via fetch (the client-side approach), handle the redirect in JavaScript instead:

if (response.ok) {
  window.location.href = '/thank-you';
}

If you use SvelteKit form actions, redirect from the server:

import { redirect } from '@sveltejs/kit';

// Inside the action, after successful submission:
redirect(303, '/thank-you');

Svelte 5 runes syntax

The primary example above uses Svelte 5's $state rune for reactive state. If your project uses Svelte 4 or earlier, replace $state with a standard let declaration:

// Svelte 4
let status = 'idle';

// Svelte 5
let status = $state('idle');

Both work identically for this use case. Svelte 4's let is already reactive in component scope. The $state rune makes reactivity explicit in Svelte 5.

Styling the form

The component above has no CSS, so it fits into any styling system. Add scoped styles in the component's <style> block, use Tailwind CSS utility classes, or apply your own CSS framework. Svelte styles are scoped to the component by default.

<style>
  form {
    max-width: 500px;
  }

  div {
    margin-bottom: 1rem;
  }

  label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 600;
  }

  input,
  textarea {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  button {
    background: #4f46e5;
    color: white;
    padding: 0.5rem 1.5rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  button:hover {
    background: #4338ca;
  }

  button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }

  .success {
    color: #16a34a;
  }

  .error {
    color: #dc2626;
  }
</style>

Why not use a SvelteKit API route?

SvelteKit supports server endpoints via +server.js files. You could build your own /api/contact endpoint. But that means:

  • Configuring an email service (SendGrid, Resend, Postmark)
  • Managing API keys in environment variables
  • Writing input validation and sanitization
  • Building a spam filter or integrating a third-party one
  • No built-in submission history or search
  • Deploying to a runtime that supports server functions (not static hosting)

A form backend handles all of this. Your SvelteKit app stays lean, and you can deploy it as a fully static site if you want.

Summary

Adding a contact form to SvelteKit takes about 2 minutes with a form backend. Create a Svelte component, point it at a FormWit endpoint, and deploy. You get spam filtering, email notifications, and a submissions dashboard with no server code. For more control, use SvelteKit form actions to proxy submissions through your own server.

FormWit's free plan includes unlimited forms, 100 submissions per month, email notifications, and built-in spam protection. Create your free account and add a contact form to your SvelteKit app today.

Related guides: JavaScript contact form · React contact form · HTML contact form · Spam protection · Contact form templates

Want to skip the setup?

FormWit gives you a form endpoint in 60 seconds. Free plan, no credit card.

Create Free Form

Need a form fast?

Build one visually with our free HTML form generator — no coding required.

Try the Form Generator →

Add FormWit to your SvelteKit app

Add a contact form to your site in 30 seconds. No backend code required.

Try FormWit Free