Forms & Actions
Forms POST to actions
Section titled “Forms POST to actions”SvelteKit handles form submissions with actions — server functions in +page.server.js that receive the submitted data. A plain HTML <form method="POST"> posts to them, so the feature works before any JavaScript loads.
import { fail, redirect } from '@sveltejs/kit';
export const actions = { default: async ({ request, cookies }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password');
if (!email || !password) { // Return a 400 with the values so the form can re-render the error. return fail(400, { email, missing: true }); }
const user = await authenticate(email, password); if (!user) return fail(401, { email, incorrect: true });
cookies.set('session', user.token, { path: '/' }); redirect(303, '/dashboard'); // throw a redirect on success },};<script> let { form } = $props(); // the action's returned value (e.g. fail data)</script>
<form method="POST"> <input name="email" value={form?.email ?? ''} /> <input name="password" type="password" /> {#if form?.incorrect}<p>Wrong email or password</p>{/if} <button>Log in</button></form>The action’s return value (including fail(...) data) arrives as the form prop. On success you redirect; on failure you fail(status, data) so the page re-renders with the error and the user’s input preserved.
Named actions
Section titled “Named actions”One page can have several actions — a login form and a register form, say. Name them and target one with action="?/name":
export const actions = { login: async ({ request }) => { /* … */ }, register: async ({ request }) => { /* … */ },};<form method="POST" action="?/login">…</form><form method="POST" action="?/register">…</form>Progressive enhancement with use:enhance
Section titled “Progressive enhancement with use:enhance”The form above already works with no JavaScript — the browser posts, the server responds, the page reloads. Add use:enhance from $app/forms and SvelteKit upgrades it to a smooth AJAX submission (no full reload), automatically updating the form prop and invalidating load data — while keeping the no-JS fallback.
<script> import { enhance } from '$app/forms';</script>
<form method="POST" use:enhance> <!-- works without JS, better with it --> …</form>flowchart TB nojs["No JS: browser POSTs the form"] --> server["action runs on the server"] js["With use:enhance: AJAX submit, no reload"] --> server server --> result["redirect on success, or fail data → form prop"]
This is SvelteKit’s signature move: build with plain forms that work everywhere, then enhance for a better experience — instead of a JS-only form that breaks without it.