Course outline

Svelte / Intermediate: Kit routes and server data policy / Lesson 9

Server actions, progressive forms and replay

Use Kit form actions as explicit authenticated mutation boundaries and keep enhancement separate from domain guarantees.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain +page.server actions, fail results and redirect control flow.
  • Validate FormData and preserve canonical outcomes across repeated submissions.

Every action needs its own current authority

A form action lives in +page.server and handles a mutation request independently of the earlier page render. A hidden ID, bound browser value or disabled submit button is still caller-controlled and does not authorize the requested object. Resolve trusted locals and apply shared domain policy before any effect. Kitโ€™s action machinery does not make the database transaction atomic with external delivery or prevent duplicate intent after a lost response. Keep expected revisions and logical operation identity separate when the workflow requires them. The server result should describe confirmed state or an explicit unresolved outcome rather than merely echoing the clientโ€™s optimistic draft.

Return expected field problems before source work

The default action reads FormData, rejects a nontext or blank title and calls a policy-aware service with the current principal. fail returns a safe expected action failure rather than a raw exception. The source service and session verification are assumed; no form or action runs here. The example uses one default action only. Named and default action conventions have specific composition rules, so do not combine copied snippets without checking the actual route contract. A successful response should expose canonical public fields and must not include passwords, internal notes or raw source error objects.

javascript Illustrative default form action
import {fail} from "@sveltejs/kit";
export const actions={
  default:async({request,locals})=>{
    const form=await request.formData();
    const title=form.get("title");
    if(typeof title!=="string" || !title.trim())return fail(400,{code:"INVALID_TITLE"});
    const course=await catalog.createFor(locals.principal,{title:title.trim()});
    return {course:{id:course.id,title:course.title}};
  }
};

The principal and catalog are unexecuted reviewed dependencies. No HTTP request, persistence or action failure was actually produced.

Progressive enhancement changes interaction, not permission

A native method=POST form targeting the appropriate page action can work without client enhancement. use:enhance adds the documented client behavior for Kit form actions; it is not a generic wrapper for every +server endpoint or arbitrary method. Custom enhancement callbacks can change updating, invalidation and reset behavior, so preserve defaults deliberately when needed. Show pending and field feedback accessibly and retain useful draft values after expected rejection. Kit 3 retains the redirect/error control flow introduced in Kit 2; a broad catch can swallow that signal. Keep navigation and revalidation after a confirmed mutation separate from whether the source committed.

Preserve omitted versus explicit clearing locally

The local patch function preserves an omitted summary, clears explicit null and accepts a supplied empty string under this synthetic policy. It copies the source instead of mutating it. The function does not parse FormData or implement an action; an actual adapter must preserve member presence and reject disallowed types before using it. A different domain may forbid null or empty text, so document the chosen rule. Do not use a truthiness shortcut that turns every empty value into omission or convert all missing fields into null and silently erase data the caller intended to preserve.

javascript Complete offline patch-presence rule
function patch(source,input){return Object.hasOwn(input,"summary")?{...source,summary:input.summary}:{...source};}
const old={summary:"Original"};
console.assert(patch(old,{}).summary==="Original");
console.assert(patch(old,{summary:null}).summary===null);
console.assert(patch(old,{summary:""}).summary==="");
console.assert(old.summary==="Original");

All four assertions follow the declared policy. No FormData conversion, action or source write is tested.

Test progressive flow and uncertain writes independently

Use separate fixtures for JavaScript-disabled submission, enhanced submission, expected validation failure, denied object, conflict and commit before lost response. A pending button cannot establish durable uniqueness. Reconcile repeated logical operations through the domain protocol and return the original recorded outcome where appropriate. Verify the actual action result signature and custom enhancement handling rather than assuming visible success means every load reran. This course executes only the patch model; native form submission, enhancement, fail serialization, redirect handling, source atomicity and browser recovery remain unexecuted. Real integration needs the selected Kit/Svelte versions, safe principals and an authorized isolated environment.

Common errors and fixes

A hidden field grants access to another course

Why it happens
Client-controlled form values were trusted as authority.
How to fix it
Resolve current principal and enforce object policy inside the action/service.
Prevent it next time
Test direct forged submissions with an ineligible principal.

Custom enhancement drops expected update behavior

Why it happens
The callback changed the default action-result lifecycle without preserving it.
How to fix it
Follow the documented update/apply contract deliberately.
Prevent it next time
Test validation, success, reset and redirect paths with enhancement enabled.

A response failure triggers a second committed effect

Why it happens
Missing confirmation was interpreted as definite nonexecution.
How to fix it
Reconcile through stable logical operation identity before replay.
Prevent it next time
Include commit-before-response-loss fixtures.

Check your understanding

1. Does use:enhance authorize the action or guarantee one write?

Show worked solution

No. It changes supported form interaction and result handling in the browser. Callers can submit directly or retry after network loss. The server must validate input, resolve current authority and use a domain replay/transaction protocol. Test enhanced and unenhanced flows without treating either as an authorization mechanism.

2. Why can fail be preferable to a raw exception for an invalid title?

Show worked solution

An invalid title is an expected input outcome that can carry safe field feedback. A raw source exception may disclose private details and obscure the distinction from an outage. Return the documented action failure shape, preserve useful draft state and still assert that no protected write happened before rejection.

Key takeaways

  • Actions are independent server mutation boundaries.
  • Enhancement, canonical outcomes and durable replay need separate contracts.

Official references