Handle form submissions on the server with actions, progressively enhance with use:enhance, and return validation errors with fail.
Why: an "action" is a server function that handles a form submit — perfect for creating or updating data without writing a separate API. Put it in +page.server.ts under actions. Note: "default" handles a form with no specific name.
// src/routes/contact/+page.server.ts
import type { Actions } from './$types'
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData()
const name = data.get('name')
// ...save it
return { success: true }
},
}Why: point a normal <form method="POST"> at the page and it calls the action. With plain HTML this works even before JavaScript loads — the form does a full submit and the page re-renders.
<!-- src/routes/contact/+page.svelte -->
<form method="POST">
<input name="name" />
<button type="submit">Send</button>
</form>Why: add use:enhance and the submit happens without a full page reload, while still working if JavaScript is off. Note: "progressive enhancement" means the basic version works everywhere, and JS makes it nicer.
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms'
</script>
<form method="POST" use:enhance>
<input name="name" />
<button type="submit">Send</button>
</form>Why: for expected problems (a missing field), don’t throw — return fail() with a status and data. The page receives it as the form prop so you can show a message and refill inputs.
// src/routes/contact/+page.server.ts
import { fail } from '@sveltejs/kit'
import type { Actions } from './$types'
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData()
const name = data.get('name')
if (!name) {
return fail(400, { error: 'Name is required' })
}
return { success: true }
},
}<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms'
let { form } = $props()
</script>
<form method="POST" use:enhance>
<input name="name" />
{#if form?.error}<p>{form.error}</p>{/if}
<button type="submit">Send</button>
</form>Why: one page often needs several actions (create, delete). Give each a name and point each button at it with formaction="?/name". Note: the ?/ prefix tells SvelteKit which action to run.
// src/routes/todos/+page.server.ts
import type { Actions } from './$types'
export const actions: Actions = {
create: async ({ request }) => {
/* ...create */
},
delete: async ({ request }) => {
/* ...delete */
},
}
// In the page:
// <button formaction="?/create">Add</button>
// <button formaction="?/delete">Remove</button>