Skip to content
AITroveRead. Build. Understand.
Make this comfortable

SvelteKit Form Actions, Validation, and Mutation Replay

Last updated: 5 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

A SvelteKit form action handles a POST on the server. The form can submit without client JavaScript, while enhancement can improve the in-browser interaction without changing the server's authority. Input validation must happen inside the action, even if the browser has required fields. A form value is untrusted text, not proof that the reviewer owns the case. The action needs authenticated identity from the request context, case-level authorization, a mutation command, and a response that distinguishes validation failure from success. Duplicate submissions remain possible: a button may be clicked twice, a response can be lost after commit, or an intermediary may retry. A stable operation ID and server-owned deduplication define the logical write.

Working case

A reviewer approves permit 62. The server commits the approval, but the connection closes before the response reaches the browser. The reviewer resubmits, and a naive action sends two audit notifications. Another user changes the hidden case ID from 62 to 81 and sees a success message because the action trusted the form field. The repaired action validates the ID and operation token, looks up the signed-in reviewer, checks permission for that exact case, and passes a stable operation ID to a service that returns the prior result on replay. The ordinary form still works when scripts are disabled; an enhanced version may show pending feedback while it waits.

Implementation boundary

typescript
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
  approve: async ({ request, locals }) => {
    const reviewer = locals.reviewer;
    if (!reviewer) return fail(401, { message: 'Sign in required' });
    const fields = await request.formData();
    const rawCaseId = fields.get('caseId');
    const operationId = fields.get('operationId');
    if (typeof rawCaseId !== 'string' || !/^[1-9]\d{0,8}$/.test(rawCaseId) ||
        typeof operationId !== 'string' || !/^[a-zA-Z0-9-]{20,80}$/.test(operationId)) {
      return fail(400, { message: 'Invalid approval request' });
    }
    const result = await locals.permits.approve({
      reviewerId: reviewer.id, caseId: Number(rawCaseId), operationId
    });
    if (result.kind === 'forbidden') return fail(403, { message: 'Access denied' });
    if (result.kind === 'conflict') return fail(409, { message: 'Case changed' });
    return { approvedCaseId: result.caseId };
  }
};

Place the approval action in a server-only page module. Parse and bound every form field, including the case ID and operation ID; do not coerce an empty string into a valid zero-like identifier. Obtain reviewer identity from server locals and return an authentication failure when it is absent. The permit service must verify authority and perform the deduplicated write atomically, not accept a client-provided reviewer ID. Return field-level failure data without echoing secrets or private records. Render a POST form with the named action and explicit submit button. If use:enhance is added, preserve normal form behavior and the action's validation result; avoid replacing errors with an optimistic success panel. A lost response is handled by replaying the same operation ID, not manufacturing a new write.

Cost and boundaries

Validation and permission checks add server work on every submission. The deduplication record consumes O(m) storage for m retained operations and needs expiry aligned with the retry window and audit requirements. An atomic approval transaction may contend on one case under concurrent reviewers; decide which winner is authoritative and surface a conflict when needed. A progressively enhanced form ships extra client code and can improve pending feedback, yet the plain POST path remains the reliability baseline. A broad automatic retry of non-idempotent writes can multiply notifications or charges. Measure duplicate logical operations, conflict rate, form completion without script, and accessible error recovery.

Failure trace

Submit with JavaScript disabled and verify the server returns a clear result. Send an empty case ID, a decimal, a negative value, an overlarge operation token, and a case belonging to another reviewer. Only a well-formed authorized command may reach the mutation service. Commit approval, drop the response, and replay the same operation ID: one status transition and one notification should remain. Use a new operation ID for a second deliberate action and check the server's conflict rule. Double click on a slow connection and inspect both requests. Enable enhancement and verify it does not hide server validation or prevent normal keyboard submission.

Verification

  • Unenhanced POST remains usable and reports field errors.
  • The action uses server identity and case-level permission.
  • A replayed operation ID produces one logical approval.

Practice drill

Create a permit approval form for case 62 with an operation token. Start with the unenhanced POST, then add a pending state if the baseline passes. Write an action that rejects malformed IDs and missing identity, delegates case permission to a server service, and submits the token for atomic deduplication. Build a test service that records committed operation IDs and returns the previous result on replay. Simulate a lost response after commit, a tampered case field, and two reviewers approving concurrently. Record the resulting status, notification count, and user-visible error for each path.

Decision note

The action validates and authorizes each POST; the server service owns atomic mutation and replay semantics.

Common Mistakes

  • Treating a hidden case ID as authorization.
  • Retrying a committed write with a fresh operation ID.
  • Letting enhancement bypass the normal server validation path.

Related lessons

Svelte Reactivity and Server Boundaries; Svelte Runes: Source State, Derived Views, and Effect Cleanup; Svelte Props, Callbacks, and Keyed Editor Ownership; SvelteKit Request-Scoped Load and Hydration State; API Mutation and Failure Contracts; Checkout and Payment State.

Apply and check

Build Project: Svelte permit review and approval action and review Web Development: Svelte reactivity and server boundaries quiz.

web-tech
web-development
Storage details