Svelte logo

SvelteKit by Example: Form Actions

Form actions handle form submissions on the server. They work even when JavaScript is disabled.

In this example, we'll create a todo and show the result in the form.

Actions need a server runtime and a server adapter. Unlike this static documentation site, a page with actions cannot be prerendered. If a parent layout enables prerendering, export prerender = false from this page's +page.js.

Provide your own database helper in src/lib/server/db.js. Its async createTodo(task) function saves a todo.

Export actions from +page.server.js. The default action handles a POST to this page.

Validate on the server. fail returns a status and data to the form without creating a todo.

After saving, return data for the page's form prop.

src/routes/todos/+page.server.js
import { createTodo } from '#lib/server/db.js';
import { fail } from '@sveltejs/kit';



/** @type {import('./$types').Actions} */
export const actions = {
  default: async ({ request }) => {
    const fields = await request.formData();
    const task = String(fields.get('task') ?? '').trim();

    if (!task) {
      return fail(400, { task, message: 'Please enter a todo.' });
    }



    await createTodo(task);
    return { task: '', message: 'Todo created!' };
  },
};

Read the action's returned data from form. On the first visit, there's no result yet.

Use method="POST" to call the default action. The input's name is the key read by formData.get on the server.

Display the validation message or confirmation returned by the action.

src/routes/todos/+page.svelte
<script>
  /** @type {import('./$types').PageProps} */
  let { form } = $props();
</script>




<form method="POST">
  <label for="task">Todo</label>
  <input id="task" name="task" value={form?.task ?? ''}>
  <button type="submit">Add todo</button>
</form>


{#if form?.message}
  <p>{form.message}</p>
{/if}

enhance submits the same form without a full page reload when JavaScript is available. Without JavaScript, it still uses a normal POST.

The default enhancement updates form, resets the form after success, and reloads page data. No custom submit handler is needed.

src/routes/todos/+page.svelte
<script>
  import { enhance } from '$app/forms';

  /** @type {import('./$types').PageProps} */
  let { form } = $props();
</script>



<form method="POST" use:enhance>
  <label for="task">Todo</label>
  <input id="task" name="task" value={form?.task ?? ''}>
  <button type="submit">Add todo</button>
</form>

{#if form?.message}
  <p>{form.message}</p>
{/if}

Actions can also be named. Rename default to create in the server script, then select it with action="?/create". Don't mix default and named actions in the same file.

src/routes/todos/+page.svelte
<script>
  import { enhance } from '$app/forms';

  /** @type {import('./$types').PageProps} */
  let { form } = $props();
</script>

<form method="POST" action="?/create" use:enhance>
  <label for="task">Todo</label>
  <input id="task" name="task" value={form?.task ?? ''}>
  <button type="submit">Add todo</button>
</form>

{#if form?.message}
  <p>{form.message}</p>
{/if}