SvelteKit

Mastering Form Actions in SvelteKit

Deep dive into SvelteKit's form actions for handling form submissions with progressive enhancement.

Mastering Form Actions in SvelteKit

Form actions are one of SvelteKit’s most powerful features. They enable progressive enhancement by default and make form handling a breeze.

What Are Form Actions?

Form actions are server-side functions that handle form submissions. They:

  • Work without JavaScript (progressive enhancement)
  • Provide type-safe request handling
  • Support multiple actions per page
  • Enable easy validation and error handling

Basic Form Action

Let’s start with a simple contact form:

// src/routes/contact/+page.server.ts
import type { Actions } from './$types';
import { fail } from '@sveltejs/kit';

export const actions: Actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = data.get('email');
    const message = data.get('message');

    // Validation
    if (!email || typeof email !== 'string') {
      return fail(400, { email, missing: true });
    }

    // Process the form
    await sendEmail(email, message);

    return { success: true };
  }
};

The corresponding form:

<script lang="ts">
  import type { ActionData } from './$types';
  import { enhance } from '$app/forms';

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

<form method="POST" use:enhance>
  <label>
    Email
    <input
      type="email"
      name="email"
      value={form?.email ?? ''}
      class:error={form?.missing}
    />
  </label>

  {#if form?.missing}
    <p class="error">Email is required</p>
  {/if}

  <label>
    Message
    <textarea name="message"></textarea>
  </label>

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

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

Named Actions

Handle multiple actions on one page:

export const actions: Actions = {
  login: async ({ request, cookies }) => {
    const data = await request.formData();
    // Handle login
  },

  register: async ({ request }) => {
    const data = await request.formData();
    // Handle registration
  },

  logout: async ({ cookies }) => {
    cookies.delete('session', { path: '/' });
    return { success: true };
  }
};

Use with the action attribute:

<form method="POST" action="?/login">
  <!-- login fields -->
</form>

<form method="POST" action="?/register">
  <!-- registration fields -->
</form>

<form method="POST" action="?/logout">
  <button>Log out</button>
</form>

Progressive Enhancement with use:enhance

The enhance action makes forms feel instant:

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

  let loading = $state(false);
</script>

<form
  method="POST"
  use:enhance={() => {
    loading = true;

    return async ({ update }) => {
      loading = false;
      await update();
    };
  }}
>
  <button disabled={loading}>
    {loading ? 'Submitting...' : 'Submit'}
  </button>
</form>

Validation with Zod

Combine with Zod for robust validation:

import { z } from 'zod';
import { fail } from '@sveltejs/kit';

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password too short')
});

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

    const result = schema.safeParse(data);

    if (!result.success) {
      const errors = result.error.flatten().fieldErrors;
      return fail(400, { errors, data });
    }

    // Process valid data
    await createUser(result.data);

    return { success: true };
  }
};

Redirecting After Success

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

export const actions: Actions = {
  default: async ({ request }) => {
    // Process form...

    throw redirect(303, '/dashboard');
  }
};

Key Takeaways

  1. Forms work without JS - Always start with progressive enhancement
  2. Use fail() for errors - Returns data while indicating failure
  3. Named actions - Multiple forms, one endpoint
  4. use:enhance - Upgrade to SPA-like experience
  5. Validate server-side - Never trust client input

Form actions make SvelteKit feel like a complete full-stack framework. They’re simple to use yet powerful enough for complex applications!