Events
A number of events give you full control over the submit process. They are triggered every time the form is submitted.
Event flowchart
Usage
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
onSubmit: ({
action, formData, formElement, controller, submitter, cancel,
jsonData, validators, customRequest
}) => 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 also three extra properties in the Superforms onSubmit
event, for more advanced scenarios:
jsonData
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
jsonData(taintedData);
}
});
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.
validators
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);
}
});
customRequest
You can make a custom request with fetch or XMLHttpRequest when submitting the form. The main use case is to display a progress bar when uploading large files.
The customRequest
option takes a function that should return a Promise<Response | XMLHttpRequest>
. In the case of an XMLHttpRequest
, the promise must be resolved after the request is complete. The response body should contain an ActionResult
, as any form action does.
import { superForm } from 'sveltekit-superforms';
import type { SubmitFunction } from '@sveltejs/kit';
let progress = 0;
function fileUploadWithProgress(input: Parameters<SubmitFunction>[0]) {
return new Promise<XMLHttpRequest>((resolve) => {
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (event) {
progress = Math.round((100 * event.loaded) / event.total);
};
xhr.onload = function () {
if (xhr.readyState === xhr.DONE) {
progress = 0;
resolve(xhr);
}
};
xhr.open('POST', input.action, true);
xhr.send(input.formData);
});
}
const { form, enhance } = superForm(data.form, {
onSubmit({ customRequest }) {
customRequest(fileUploadWithProgress)
}
});
onResult
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') {
goto(result.location);
}
}
});
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
.
onUpdate
onUpdate: ({ form, formElement, cancel, result }) => 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
.
If your app is a single-page application, onUpdate
is the most convenient to process the form data. See the SPA page for more details.
You can also access the ActionResult
in result
, which is narrowed to type 'success'
or 'failure'
here. You can use it together with the FormResult
helper type to more conventiently access any additional form action data:
import { superForm, type FormResult } from 'sveltekit-superforms';
import type { ActionData, PageData } from './$types.js';
let { data } : { data: PageData } = $props();
const { form, errors, message, enhance } = superForm(data.form, {
onUpdate({ form, result }) {
const action = result.data as FormResult<ActionData>;
// If you've returned from the form action:
// return { form, extra: 123 }
if (form.valid && action.extra) {
// Do something with the extra data
}
}
});
onUpdated
onUpdated: ({ form }) => void
If 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. Unlike the previous events, $form
, $errors
and the other stores now contain updated data.
Example
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
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 its error
property:
App.Error | Error | { message: string }
Depending on what kind of error occurs, it will have a different shape.
Error type | Shape |
---|---|
Expected error | App.Error |
Server exception | { message: string } |
JSON response | Unexpected JSON responses, such as from a proxy server, should be included in App.Error to be type-safe. |
Other response | If a fetch fails, or HTML is returned for example, result.error will be of type Error (MDN), usually with a JSON parse error. It has a message property. |
In this simple example, the message store is set to the error message or a fallback:
const { form, enhance, message } = superForm(data.form, {
onError({ result }) {
$message = result.error.message || "Unknown error";
}
});
If JSON is returned, the HTTP status code will be taken from its status
property, instead of the default status 500
for unexpected errors.
You can also set onError
to the string value 'apply'
, in which case the default SvelteKit 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
onChange
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(event.target) {
// Form input event
console.log(
event.path, 'was changed with', event.target,
'in form', event.formElement
);
} else {
// Programmatic event
console.log('Fields updated:', event.paths)
}
}
})
If you want to handle all change events, you can access event.paths
without distinguishing.