Forms and actions
An action is a server function that receives a submitted HTML form. It is declared in +page.server.js next to the form's page. The form works even without JavaScript in the browser, and with it — without reloading the page.
A page with a form
Create src/routes/blog/new/+page.svelte:
<!-- src/routes/blog/new/+page.svelte -->
<h1>New post</h1>
<form method="POST">
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Text"></textarea>
<button>Publish</button>
</form>The method="POST" attribute is required: actions respond only to POST requests. The form appears at http://localhost:5173/blog/new, but there is nowhere to send it yet.
The action
Create src/routes/blog/new/+page.server.js:
// src/routes/blog/new/+page.server.js
import { redirect } from '@sveltejs/kit';
import { addPost } from '$lib/server/posts.js';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const title = data.get('title');
const body = data.get('body');
const slug = title.toLowerCase().replaceAll(' ', '-');
addPost({ slug, title, body });
redirect(303, `/blog/${slug}`);
}
};The default action reads the form data from request.formData(), saves the post and redirects to its page. Fill in the form and click "Publish" — the new post opens, and it appears in the list at /blog. Code 303 tells the browser to open the new address with a regular GET request.
Validating data
If the title is empty, return the form with an error using the fail function:
// src/routes/blog/new/+page.server.js (start of the action)
import { fail, redirect } from '@sveltejs/kit';
// inside default, right after reading the fields:
if (!title) {
return fail(400, { body, missing: true });
}The object from fail arrives on the page in the form prop:
<!-- src/routes/blog/new/+page.svelte -->
<script>
let { form } = $props();
</script>
<form method="POST">
{#if form?.missing}<p class="error">Enter a title</p>{/if}
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Text">{form?.body ?? ''}</textarea>
<button>Publish</button>
</form>Submit the form without a title — "Enter a title" appears above the field, and the typed text is kept.
use:enhance
Without JavaScript the browser reloads the page on submit. The use:enhance directive from $app/forms sends the form in the background and updates form and the page data in place:
<!-- src/routes/blog/new/+page.svelte -->
<script>
import { enhance } from '$app/forms';
let { form } = $props();
</script>
<form method="POST" use:enhance>
<input name="title" placeholder="Title" />
<button>Publish</button>
</form>The behaviour is the same, but without the page flashing. If JavaScript is turned off, the form keeps working the usual way.
Common mistake
Declaring the action in +page.js instead of +page.server.js. Actions run only on the server, and SvelteKit does not find them there: submitting the form ends with a 405 "Method Not Allowed" error. Export the actions object from +page.server.js.