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
- Forms work without JS - Always start with progressive enhancement
- Use
fail()for errors - Returns data while indicating failure - Named actions - Multiple forms, one endpoint
use:enhance- Upgrade to SPA-like experience- 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!