A number of events give you full control over the submit process. They are triggered every time the form is submitted.

Event flowchart


const { form, enhance } = superForm(data.form, {
  onSubmit: ({ action, formData, formElement, controller, submitter, cancel }) => void
  onResult: ({ result, formEl, cancel }) => void
  onUpdate: ({ form, cancel }) => void
  onUpdated: ({ form }) => void
  onError: (({ result, message }) => void) | 'apply'


onSubmit: ({
  action, formData, formElement, controller, submitter, cancel,
  jsonData, validators
}) => void;

The onSubmit event is the first one triggered, being essentially the same as SvelteKit’s own use:enhance function. It gives you a chance to cancel the submission altogether. See the SvelteKit docs for most of the SubmitFunction signature. There are two extra properties in the Superforms onSubmit event:


If you’re using nested data, the formData property cannot be used to modify the posted data, since $form is serialized and posted instead. If you want to post something else than $form, you can do it with the jsonData function:

import { superForm, type FormPath } from 'sveltekit-superforms'

const { form, enhance, isTainted } = superForm(data.form, {
  dataType: 'json',
  onSubmit({ jsonData }) {
    // Only post tainted (top-level) fields
    const taintedData = Object.fromEntries(
      Object.entries($form).filter(([key]) => {
        return isTainted(key as FormPath<typeof $form>)
    // Set data to be posted

Note that if client-side validation is enabled, it’s always $form that will be validated. Only if it passes validation, the data sent with jsonData will be used. It does not work in SPA mode either, as data transformation can be handled in onUpdate in that case.


For advanced validation, you can change client-side validators for the current form submission with this function.

import { superForm } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { customSchema } from './schemas.js';

let step = 1;
const lastStep = 5;

const { form, enhance } = superForm(data.form, {
  onSubmit({ validators }) {
    if(step == 1) validators(zod(customSchema));
    else if(step == lastStep) validators(false);


onResult: ({ result, formElement, cancel }) => void

If the submission isn’t cancelled and client-side validation succeeds, the form is posted to the server. It then responds with a SvelteKit ActionResult, triggering the onResult event.

result contains the ActionResult. You can modify it; changes will be applied further down the event chain. formElement is the HTMLFormElement of the form. cancel() is a function which will cancel the rest of the event chain and any form updates.

Common usage

onResult is useful when you have set applyAction = false and still want to redirect, since the form doesn’t do that automatically in that case. Then you can do:

const { form, enhance } = superForm(data.form, {
  applyAction: false,
  onResult({ result }) {
    if (result.type === 'redirect') {

Also, if applyAction is false, which means that $page.status won’t update, you’ll find the status code for the request in result.status.

Strongly typed ActionResult

Usually, you check the ActionResult status in onResult, not the form validation result, which is more conveniently handled in onUpdate (see below). But if you return additional data in the form action, there is a helper type called FormResult, that you can use to make the ActionResult data strongly typed:

<script lang="ts">
  import { superForm, type FormResult } from 'sveltekit-superforms';
  import type { ActionData } from './$types.js';

  export let data;

  const { form, enhance } = superForm(data.form, {
    onResult: (event) => {
      const result = event.result as FormResult<ActionData>;
      if (result.type == 'failure') {
        // Strongly typed now (but quite unreadable,
        // prefer to use onUpdate or onUpdated)


onUpdate: ({ form, formElement, cancel }) => void

The onUpdate event is triggered right before the form update is being applied, giving you the option to modify the validation result in form, or use cancel() to negate the update altogether. You also have access to the form’s HTMLFormElement with formElement.

When you don’t need to modify or cancel the validation result, the last event is the most convenient to use:


onUpdated: ({ form }) => void

When you just want to ensure that the form is validated and do something extra afterwards, like showing a toast notification, onUpdated is the easiest way.

The form parameter contains the validation result, and should be considered read-only here, since the stores have updated at this point.


const { form, enhance } = superForm(data.form, {
  onUpdated({ form }) {
    if (form.valid) {
      // Successful post! Do some more client-side stuff,
      // like showing a toast notification.
      toast(form.message, { icon: '✅' });


onError: (({ result }) => void) | 'apply'

When the SvelteKit error function is called on the server, you can use the onError event to catch it.

result is the error ActionResult, with the error property casted to App.Error. In this simple example, the message store is set to the error:

const { form, enhance, message } = superForm(data.form, {
  onError({ result }) {
    $message = result.error.message;

You can also set onError to the string value 'apply', in which case the SvelteKit applyAction error behaviour will be used, which is to render the nearest +error page, also wiping out the form. To avoid data loss even for non-JavaScript users, returning a status message instead of throwing an error is recommended.

Non-submit events


The onChange event is not triggered when submitting, but every time $form is modified, both as a html event (when modified with bind:value) and programmatically (direct assignment to $form).

The event is a discriminated union that you can distinguish between using the target property:

const { form, errors, enhance } = superForm(data.form, {
  onChange(event) {
    if( {
      // Form input event
        event.path, 'was changed from',, 
        'in form', event.formElement
    } else {
      // Programmatic event
      console.log('Fields updated:', event.paths)

If you only want to handle programmatic events, you can access event.paths without distinguishing.