SvelteKit contact forms on a static site

Send Svelte and SvelteKit forms to your email without form actions or a server adapter. A Svelte 5 component that works with adapter-static and without JavaScript.

Free for 100 submissions a month, no credit card. Updated

SvelteKit's form actions are the obvious way to handle a form, but they run on the server. Build with adapter-static and they're gone. Posting to a Nisuform endpoint works with any adapter, because the browser sends the form straight to the endpoint, which filters spam, stores the submission and emails you.

SvelteKit form actionsNisuform endpoint
Server adapterRequired, like adapter-node or adapter-vercelNot needed, works with adapter-static
EmailYou add a provider and an API keyIncluded
Spam filteringYou write itHoneypot, fill-time check, rate limits, Turnstile or reCAPTCHA
Stored submissionsYou add a databaseSearchable inbox with CSV export
Works without JavaScriptYesYes

Keep form actions for work your server has to do, like creating an account. A contact form only has to reach you.

The component

src/lib/ContactForm.svelte
<script lang="ts">
  const endpoint = 'https://api.nisuform.com/s/YOUR_FORM_KEY'
  const shownAt = Date.now()
  let status = $state<'idle' | 'sending' | 'sent' | 'error'>('idle')
  let error = $state('')

  async function submit(event: SubmitEvent) {
    event.preventDefault()
    const data = new FormData(event.currentTarget as HTMLFormElement)
    data.set('_elapsed', String(Date.now() - shownAt))
    status = 'sending'
    try {
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: { Accept: 'application/json' },
        body: data,
      })
      const result = await response.json()
      if (result.ok) {
        status = 'sent'
      } else {
        error = result.message
        status = 'error'
      }
    } catch {
      error = 'Something went wrong. Please try again.'
      status = 'error'
    }
  }
</script>

{#if status === 'sent'}
  <p role="status">Thanks! We got your message.</p>
{:else}
  <form action={endpoint} method="POST" onsubmit={submit}>
    <label>
      Email
      <input name="email" type="email" autocomplete="email" required />
    </label>
    <label>
      Message
      <textarea name="message" rows="5" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display: none" />
    <button type="submit" disabled={status === 'sending'}>
      {status === 'sending' ? 'Sending...' : 'Send'}
    </button>
    {#if status === 'error'}
      <p role="alert">{error}</p>
    {/if}
  </form>
{/if}

With action and method set, this is a working HTML form before any JavaScript runs. Once the component hydrates, onsubmit takes over and the visitor sees the result without leaving the page. Render it from any route:

src/routes/contact/+page.svelte
<script lang="ts">
  import ContactForm from '$lib/ContactForm.svelte'
</script>

<h1>Contact</h1>
<ContactForm />

Create a form and paste the key from its Setup tab into endpoint. Every input with a name is saved, even ones you never added in the dashboard. Run npm run dev, wait two seconds and send a test.

Any adapter, or none

The component works the same however the page is built. With adapter-static or export const prerender = true, the page is a file and the form posts from the browser, so GitHub Pages, Cloudflare Pages and Netlify all work. With adapter-node, adapter-vercel or adapter-cloudflare it behaves the same, and you can still use form actions on other routes.

Plain Svelte in a Vite app or an Astro island uses the same file. Import it from wherever you keep it instead of $lib.

When the site is live, add your domain and http://localhost:5173 to the form's allowed websites.

What you get with Nisuform

Free covers 100 submissions a month across 3 forms. Pro is $10 a month for 5,000 submissions and adds the features marked Pro.

  • Spam filtering on every plan

    A honeypot, a fill-time check, rate limits and duplicate checks run on every submission, with optional Cloudflare Turnstile or Google reCAPTCHA. Spam never counts toward your limit.

  • An email for every submission

    You get an email within seconds of each submission. Reply-to is set to the visitor, so you can answer from your mail app.

  • Every submission kept

    Search, star and export to CSV. Submissions stay until you delete them, so a missed email never loses a message.

  • File uploadsPro

    Up to 5 images or PDFs per submission, stored privately and shared through links that expire.

  • Slack, Discord, Sheets and webhooksPro

    Send each submission to Slack, Discord, Telegram or Google Sheets, or anywhere else with signed webhooks, Zapier or Make.

  • Auto-repliesPro

    Send each visitor a confirmation in your own words, from your own sender name.

Svelte guides

Frequently asked questions

Can I use SvelteKit form actions with adapter-static?

No. Form actions run on the server, and adapter-static builds a site with no server. A form that posts to a form backend endpoint works on a static site, because the browser sends it straight to the endpoint.

Do I need form actions to send email from SvelteKit?

No. Form actions are for when your server does something with each submission. A contact form can post to a form backend instead, without any server code.

Does the form work without JavaScript?

Yes. The endpoint is the form's action, so before JavaScript loads, or if it never does, the browser posts the form and the visitor sees the thank-you page. With JavaScript, the component submits in place.

Does it work with Svelte 4?

The example uses Svelte 5 runes and the onsubmit attribute. In Svelte 4, use let variables and on:submit|preventDefault. The request is the same.

Is the form key safe in client code?

Yes. It only accepts new submissions, and nobody can read your submissions or change the form with it. Add your domains to the form's allowed websites to stop other sites from posting to it.

Start collecting form submissions

Set up your forms with built-in spam protection in minutes for free.

Jane Smith

jane@smith.design

Live

12.4k

Submissions

24

Forms

0

Spam