Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.

Thanks for reading!

Donate to the site — we work hard for you.