Lesson 19 / 25

Form Actions and use:enhance

Progressively enhanced writes.

Forms that work without JavaScript

A +page.server.ts can export actions: a default action or named actions targeted with action="?/create". An action receives the request (read await request.formData()), cookies, locals and params. Return data to show a result, or fail(400, { ... }) to return a validation failure with a status code; the page reads the latest result through the form prop. A plain <form method="POST"> works with JavaScript disabled. Adding use:enhance from $app/forms progressively enhances it: the form submits with fetch, updates form, reruns load functions and avoids a full page reload. You can pass a callback to enhance to show pending states or customise the update.

The server side of SvelteKit

Form actions handle writes, +server.ts files expose HTTP endpoints, and hooks run on every request.

Three ideas: form actions, API routes and hooks, environment variables.
Figure 7.1 — Request, hooks, actions or endpoints, response.

A validated signup action

Server action plus enhanced form.

<!-- src/routes/signup/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { createUser } from '$lib/server/users';

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = String(data.get('email') ?? '');
    if (!email.includes('@')) {
      return fail(400, { email, error: 'Enter a valid email' });
    }
    await createUser(email);
    redirect(303, '/welcome');
  }
} satisfies Actions;
-->

<!-- src/routes/signup/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import type { PageProps } from './$types';
  let { form }: PageProps = $props();
  let pending = $state(false);
</script>

<form method="POST" use:enhance={() => {
  pending = true;
  return async ({ update }) => { await update(); pending = false; };
}}>
  <input name="email" type="email" value={form?.email ?? ''} />
  {#if form?.error}<p role="alert">{form.error}</p>{/if}
  <button disabled={pending}>{pending ? 'Signing up...' : 'Sign up'}</button>
</form>

Validate on the server always

Browser validation attributes improve the experience, but the action must validate again because requests can be sent without your form. A schema library such as Zod helps.

Quick check: What does use:enhance add to a SvelteKit form?

  • It is required or the form cannot submit
  • Server-side validation
  • Automatic database writes
  • Submission via fetch without a full page reload, while still working without JavaScript
Answer

Submission via fetch without a full page reload, while still working without JavaScript — Progressive enhancement layers on top of plain HTML forms.