Skip to content

Instantly share code, notes, and snippets.

@ckng
Last active May 3, 2025 17:37
Show Gist options
  • Save ckng/79fc9c29796e82fe207cb800d988015c to your computer and use it in GitHub Desktop.
Save ckng/79fc9c29796e82fe207cb800d988015c to your computer and use it in GitHub Desktop.
sveltekit-superforms docs llms.txt
Directory Structure:
└── ./
└── src
└── routes
├── api
│ └── +page.md
├── components
│ └── +page.md
├── concepts
│ ├── client-validation
│ │ └── +page.md
│ ├── enhance
│ │ └── +page.md
│ ├── error-handling
│ │ └── +page.md
│ ├── events
│ │ └── +page.md
│ ├── files
│ │ └── +page.md
│ ├── messages
│ │ └── +page.md
│ ├── multiple-forms
│ │ └── +page.md
│ ├── nested-data
│ │ └── +page.md
│ ├── proxy-objects
│ │ └── +page.md
│ ├── snapshots
│ │ └── +page.md
│ ├── spa
│ │ └── +page.md
│ ├── strict-mode
│ │ └── +page.md
│ ├── submit-behavior
│ │ └── +page.md
│ ├── tainted
│ │ └── +page.md
│ └── timers
│ └── +page.md
├── contributing
│ └── +page.md
├── crud
│ └── +page.md
├── default-values
│ └── +page.md
├── examples
│ └── +page.md
├── faq
│ └── +page.md
├── flash-messages
│ └── +page.md
├── formsnap
│ └── +page.md
├── get-started
│ └── [...lib]
│ └── +page.md
├── legacy
│ └── +page.md
├── migration
│ └── +page.md
├── migration-v2
│ └── +page.md
├── rate-limiting
│ └── +page.md
├── sponsors
│ └── +page.md
├── super-debug
│ └── +page.md
├── support
│ └── +page.md
├── whats-new-v1
│ └── +page.md
├── whats-new-v2
│ └── +page.md
└── +page.md
---
File: /src/routes/api/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# API reference
<Head title="API reference" />
Throughout the reference, the type `T` represents the type of the validation schema, extending `Record<string, unknown>`. For example, in a form with name and email, name being optional:
```ts
type T = {
name?: string | undefined,
email: string
};
```
The `Nested<T, R>` type replaces all primitive values of `T` with `R`, and removes any optional modifier. In the above example:
```ts
type Nested<T, string[]> = {
name: string[],
email: string[]
};
```
The type `M` represents the [status message](/concepts/messages/) type, default `any`.
```ts
type M = any;
```
A `ValidationAdapter<T, In>` and `ClientValidationAdapter<T, In>` are the adapters used to wrap the schema, based on the selected validation library. `In` is the input type of the schema, as transformations and pipes can make it differ from `T`, but usually they are the same. Example:
```ts
import type { Infer, InferIn } from 'sveltekit-superforms';
import { zod, zodClient } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(3)
})
// Type is now ValidationAdapter<Infer<typeof schema>, InferIn<typeof schema>>
// Which is the same as ValidationAdapter<{name: string}, {name: string}>
const adapter = zod(schema);
```
## Server API
```ts
import {
superValidate,
actionResult,
defaultValues,
message,
setError,
fail,
withFiles
} from 'sveltekit-superforms';
```
### superValidate(adapter | data, adapter? | options?, options?)
If you want the form to be initially empty, you can pass the adapter as the first parameter:
```ts
superValidate<T, M = any, In = T>(
adapter: ValidationAdapter<T, In>,
options?: SuperValidateOptions
): Promise<SuperValidated<T, M, In>>
```
If you want to populate the form, for example, from a database, `URL` parameters in the load function, or `FormData` in the form actions, send the data as the first parameter and the adapter second:
```ts
superValidate<T, M = any, In = T>(
data:
| RequestEvent
| Request
| FormData
| URL
| URLSearchParams
| Partial<In>
| null
| undefined,
adapter: ValidationAdapter<T, In>,
options?: SuperValidateOptions
): Promise<SuperValidated<T, M, In>>
```
### superValidate options
```ts
SuperValidateOptions = Partial<{
errors: boolean; // Add or remove errors from output (valid status is always preserved)
id: string; // Form id, for multiple forms support. Set automatically by default
preprocessed: (keyof T)[]; // Bypass superValidate data coercion for posted fields in this array
defaults: T; // Override default values from the schema
jsonSchema: JSONSchema; // Override JSON schema from the adapter
strict: boolean; // If true, validate exactly the posted data, no defaults added
allowFiles: boolean; // If false, set all posted File objects to undefined
transport: Transport; // Set to a transport object to send any type to the client. See https://svelte.dev/docs/kit/hooks#Universal-hooks-transport
}>
```
See the page about [multiple forms](/concepts/multiple-forms) for information about when to use `id`.
### superValidate return type
```ts
SuperValidated<T, M = any, In = T> = {
id: string;
valid: boolean;
posted: boolean;
data: T;
errors: Nested<T, string[] | undefined>;
constraints?: Nested<T, InputConstraints | undefined>;
message?: M;
};
```
If data is empty, a `SuperValidated` object with default values for the schema is returned:
```js
{
id: string;
valid: false;
posted: false;
errors: options.errors ? Nested<T, string[] | undefined> : {};
data: T;
constraints: Nested<T, InputConstraints>;
}
```
See [this page](/default-values) for a list of default schema values.
### Input constraints
```ts
/**
* HTML input constraints returned from superValidate
* Properties are mapped from the schema:
*/
InputConstraints = Partial<{
required: boolean; // Not nullable or optional
pattern: string; // The *first* string validator with RegExp pattern
min: number | string; // number or ISO date string depending on type
max: number | string; // number or ISO date string depending on type
step: number | 'any'; // number validator with step constraint
minlength: number; // string validator
maxlength: number; // string validator
}>;
```
### setError(form, field, error, options?)
```ts
setError(
form: SuperValidated<T, M, In>,
field: '' | FormPathLeaves<T>,
error: string | string[],
options?: { overwrite = false, status : ErrorStatus = 400 }
) : ActionFailure<{form: SuperValidated<T, M, In>}>
```
For setting errors on the form after validation. It returns a `fail(status, { form })` so it can be returned immediately, or more errors can be added by calling it multiple times before returning. `form.valid` will also be set to `false`.
Use the `overwrite` option to remove all previously set errors for the field, and `status` to set a different status than the default `400` (which must be in the range 400-599).
- To set a form-level error, the `field` argument can be skipped, or set to an empty string.
- To set an array-level error, append `._errors` to the field parameter, like `"tags._errors"`.
### message(form, message, options?)
```ts
message(
form: SuperValidated<T, M, In>,
message: M,
options?: { status? : NumericRange<400, 599> }
) : { form: SuperValidated<T, M, In> } | ActionFailure<{form: SuperValidated<T, M, In>}>
```
`message` is a convenience method for setting `form.message`, best explained by an example:
```ts
import { message, superValidate } from 'sveltekit-superforms/server';
export const actions = {
default: async (event) => {
const form = await superValidate<typeof schema, string>(event, schema);
if (!form.valid) {
// Will return fail(400, { form }) since form isn't valid
return message(form, 'Invalid form');
}
if (form.data.email.includes('spam')) {
// Will return fail and set form.valid = false, since status is >= 400
return message(form, 'No spam please', {
status: 403
});
}
// Returns { form }
return message(form, 'Valid form!');
}
};
```
Note that the `status` option must be in the range `400-599`.
### defaultValues(schema)
Returns the default values for a schema, either the [Superforms defaults](/default-values) or the ones you set on the schema yourself.
```ts
import { defaultValues } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2),
tags: z.string().min(1).array().default(['a', 'b'])
});
// Returns { name: '', tags: ['a', 'b'] }
const defaults = defaultValues(zod(schema));
```
This corresponds to the `form.data` returned from `const form = await superValidate(zod(schema))`.
### actionResult(type, data?, options? | status?)
When using an [endpoint](https://kit.svelte.dev/docs/routing#server) (a `+server.ts` file) instead of form actions, you must return an `ActionResult` to a form that has `use:enhance` applied. Anything else won't work, not even throwing a redirect, since superForm expects an ActionResult.
The `actionResult` function helps you construct one in a `Response` object, so you can return a validation object from your API/endpoints.
```ts
import { actionResult } from 'sveltekit-superforms';
actionResult('success', { form }, 200);
actionResult('failure', { form }, 400);
actionResult('redirect', '/', 303);
actionResult('error', 'Error message', 500);
```
The default status codes for each result type are shown, so you don't need to include them if they're the same.
Additionally, the `redirect` version can send a flash message as a third parameter, in case you're using [flash messages](/flash-messages). It can also set options for the flash cookie that's being set.
```ts
actionResult('redirect', '/', {
message: { type: 'success', text: 'Posted successfully!' },
cookieOptions: { sameSite: 'lax' }
});
```
#### Login request example
**src/routes/login/+server.ts**
```ts
import { actionResult, superValidate, setMessage } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(5)
});
export const POST = async ({ request }) => {
const form = await superValidate(request, zod(loginSchema));
if (!form.valid) return actionResult('failure', { form });
// TODO: Verify login here //
setMessage(form, 'Login successful!');
return actionResult('success', { form });
};
```
Then in the form, simply point the form action to the endpoint (and remember `use:enhance`):
```svelte
<form method="POST" action="/login" use:enhance>
<!-- Business as usual -->
```
## Client API
```ts
import {
superForm,
defaults
} from 'sveltekit-superforms';
```
### superForm(form, options?)
```ts
superForm<T, M = any>(
form: SuperValidated<T, M, In> | null | undefined,
options?: FormOptions<T, M, In>
) : SuperForm<T, M>
```
### superForm options
```ts
type FormOptions<T, M, In> = Partial<{
// Basics
id: string;
applyAction: boolean;
invalidateAll: boolean | 'force';
resetForm: boolean | (() => boolean);
taintedMessage: boolean | string | (() => Promise<boolean>);
dataType: 'form' | 'json';
multipleSubmits: 'prevent' | 'allow' | 'abort';
SPA: true;
// Error handling
scrollToError: 'auto' | 'smooth' | 'off' | boolean | ScrollIntoViewOptions;
autoFocusOnError: 'detect' | boolean;
errorSelector: string;
selectErrorText: boolean;
stickyNavbar: string;
// Events
onSubmit: (
submit: Parameters<SubmitFunction>[0] & {
jsonData: (data: Record<string, unknown>) => void,
validators: (validators: ValidationAdapter<Partial<T>, Record<string, unknown>> | false) => void,
customRequest: (input: Parameters<SubmitFunction>[0]) => Promise<Response | XMLHttpRequest>
}
) => MaybePromise<unknown | void>;
onResult: (event: {
result: ActionResult;
formEl: HTMLFormElement;
cancel: () => void;
}) => MaybePromise<unknown | void>;
onUpdate: (event: {
form: SuperValidated<T, M, In>;
formEl: HTMLFormElement;
cancel: () => void;
result: Extract<ActionResult, { type: 'success' | 'failure' }>;
}) => MaybePromise<unknown | void>;
onUpdated: (event: {
form: Readonly<SuperValidated<T, M, In>>;
}) => MaybePromise<unknown | void>;
onChange: (event: ChangeEvent) => void;
onError:
| 'apply'
| ((event: {
result: {
type: 'error';
status?: number;
error: App.Error | Error | { message: string };
};
message: Writable<SuperValidated<T, M, In>['message']>;
}) => MaybePromise<unknown | void>);
// Client-side validation
validators:
| ClientValidationAdapter<Partial<T>, Record<string, unknown>>
| ValidationAdapter<Partial<T>, Record<string, unknown>>
| false
| 'clear';
validationMethod: 'auto' | 'oninput' | 'onblur' | 'onsubmit';
clearOnSubmit: 'errors-and-message' | 'message' | 'errors' | 'none';
delayMs: number;
timeoutMs: number;
// For handling arbitrary types in the form. (See nested data)
transport: Transport;
// Disable warnings
warnings: {
duplicateId?: boolean;
};
}>;
type ChangeEvent<T> =
{
path: FormPath<T>;
paths: FormPath<T>[];
formElement: HTMLFormElement;
target: Element;
set: <Path extends FormPath<T>>(
path: Path,
value: FormPathType<T, Path>,
options?: { taint?: boolean | 'untaint' | 'untaint-form' }
) => void;
get: <Path extends FormPath<T>>(path: Path) => FormPathType<T, Path>;
} | {
target: undefined;
paths: FormPath<T>[];
set: <Path extends FormPath<T>>(
path: Path,
value: FormPathType<T, Path>,
options?: { taint?: boolean | 'untaint' | 'untaint-form' }
) => void;
get: <Path extends FormPath<T>>(path: Path) => FormPathType<T, Path>;
};
```
- See [SubmitFunction](https://kit.svelte.dev/docs/types#public-types-submitfunction) for details about the `onSubmit` arguments, and [ActionResult](https://kit.svelte.dev/docs/types#public-types-actionresult) for `onResult`.
- See [SPA action form](/concepts/spa#spa-action-form) for details about the `string` value for the `SPA` option.
- See the SvelteKit [transport](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) feature for how to send any type of form data to the server, using the `transport` option.
### superForm return type
```ts
SuperForm<T, M = any, In = T> = {
form: {
subscribe: (data: T) => void
set: (value: T, options?: { taint?: boolean | 'untaint' | 'untaint-form' }) => void
update: (updater: (T) => T, options?: { taint?: boolean | 'untaint' | 'untaint-form' }) => void
};
errors: Writable<Nested<T, string[] | undefined>>;
constraints: Writable<Nested<T, InputConstraints | undefined>>;
message: Writable<M | undefined>;
tainted: Writable<Nested<T, boolean | undefined> | undefined>;
submitting: Readable<boolean>;
delayed: Readable<boolean>;
timeout: Readable<boolean>;
posted: Readable<boolean>;
formId: Writable<string>;
allErrors: Readable<{ path: string; messages: string[] }[]>;
options: FormOptions<T, M, In>;
enhance: (el: HTMLFormElement, events?: {
onSubmit, onResult, onError, onUpdate, onUpdated
}) => ReturnType<typeof $app/forms/enhance>;
reset: (options?: {
keepMessage?: boolean;
id?: string;
data?: Partial<T>;
newState?: Partial<T>;
}) => void;
isTainted: (path?: FormPath<T> | TaintedFields<T> | boolean) => boolean;
submit: (submitter?: HTMLElement | Event | EventTarget | null | undefined) => void;
capture: () => SuperFormSnapshot<T, M>;
restore: (snapshot: SuperFormSnapshot<T, M>) => void;
validateForm: (opts?: {
update?: boolean;
schema?: ValidationAdapter<Partial<T>>;
focusOnError?: boolean;
}) => Promise<SuperValidated<T, M, In>>;
validate: (path: FormPathLeaves<T>, opts?: {
value: FormPathType<FormPathLeaves<T>>;
update: boolean | 'errors' | 'value';
taint: boolean | 'untaint' | 'untaint-form';
errors: string | string[];
}) => Promise<string[] | undefined>;
};
```
### reset
Calling the reset method without any arguments will reset the form to its initial values, but you can also reset to a different set of data, which is useful if the form data should change depending on external events.
**Options:**
```ts
{
keepMessage?: boolean;
id?: string;
data?: Partial<T>;
newState?: Partial<T>;
}
```
You have the option to keep the [status message](/concepts/messages) and change the [form id](/concepts/multiple-forms), and reset to new data with the `data` option. The `newState` option also updates the initial reset data, so when you call `reset` in the future, it will reset to that state. So if you want the new data to be the base for future resets, set both `data` and `newState`.
### defaults
The `defaults` function can be useful on the client in Svelte components and [SPA mode](/concepts/spa), since components cannot have top-level `await`.
```ts
defaults<T, M = any>(
data:
| Partial<T>
| null
| undefined,
schema: ClientValidationAdapter<T>,
options?: SuperValidateOptions
): SuperValidated<T, M, In>
```
## Proxy objects
```ts
import {
// The primitives return a Writable<string>:
booleanProxy,
dateProxy,
intProxy,
numberProxy,
stringProxy,
// File proxies
fileProxy,
fileFieldProxy, // formFieldProxy
// File[] proxies
filesProxy,
filesFieldProxy, // arrayProxy
// The type of the others depends on the field:
formFieldProxy,
arrayProxy,
fieldProxy
} from 'sveltekit-superforms';
```
A proxy handles bi-directional updates and data transformation of a corresponding form field. Updates in either the proxy or data it points to, will reflect in the other.
### intProxy(form, fieldName, options?)
Creates a string store for an **integer** field in the schema. It's rarely needed as Svelte [handles this automatically](https://svelte.dev/tutorial/numeric-inputs) with `bind:value`.
```ts
import { superForm, intProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
const proxy = intProxy(form, 'field', { options });
```
**Options:**
```ts
{
empty?: 'null' | 'undefined' | 'zero';
initiallyEmptyIfZero?: boolean;
taint?: boolean | 'untaint' | 'untaint-form';
}
```
Use the `empty` option to set the field to some of the defined values. Use `initiallyEmptyIfZero` to set the field to empty if the initial value is zero, to show the placeholder text, usually in combination with the `empty` option being set to `'zero'`.
### numberProxy(form, fieldName, options?)
Creates a string store for a **number** field in the schema. It's rarely needed as Svelte [handles this automatically](https://svelte.dev/tutorial/numeric-inputs) with `bind:value`.
```ts
import { superForm, numberProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
const proxy = numberProxy(form, 'field', { options });
```
**Options:**
```ts
{
empty?: 'null' | 'undefined' | 'zero';
initiallyEmptyIfZero?: boolean;
taint?: boolean | 'untaint' | 'untaint-form';
}
```
Use the `empty` option to set the field to some of the defined values. Use `initiallyEmptyIfZero` to set the field to empty if the initial value is zero, to show the placeholder text, usually in combination with the `empty` option being set to `'zero'`.
### booleanProxy(form, fieldName, options?)
Creates a string store for a **boolean** schema field. The option can be used to change what string value should represent `true`. An empty string always represents `false`.
```ts
import { superForm, booleanProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
const proxy = booleanProxy(form, 'field', { options });
```
**Options:**
```ts
{
trueStringValue = 'true';
taint?: boolean | 'untaint' | 'untaint-form';
}
```
### dateProxy(form, fieldName, options?)
Creates a string store for a **Date** schema field. The option can be used to change the proxied string format of the date.
```ts
import { superForm, dateProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
const proxy = dateProxy(form, 'field', { options });
```
**Options:**
```ts
{
format:
// Extract the part of the date as a substring:
| 'date' | 'datetime' | 'time'
// Convert the date to UTC:
| 'date-utc' | 'datetime-utc' | 'time-utc'
// Convert the date to local time:
| 'date-local' | 'datetime-local' | 'time-local'
// The default ISODateString:
| 'iso' = 'iso';
empty?: 'null' | 'undefined';
taint?: boolean | 'untaint' | 'untaint-form';
}
```
### stringProxy(form, fieldName, options)
Creates a string store for a **string** schema field. It may look redundant, but the option can be useful if you need to convert an empty string to `null` or `undefined`.
```ts
import { superForm, stringProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
const proxy = stringProxy(form, 'field', { options });
```
**Options:**
```ts
{
empty: 'null' | 'undefined';
taint?: boolean | 'untaint' | 'untaint-form';
}
```
### formFieldProxy(superForm, fieldName, options)
Proxies a form field, returning stores similar to `superForm` but for a single field. For arrays in the schema, see below for how to create an `arrayProxy`.
> The whole object returned from `superForm` is required here, not just the `$form` store.
```svelte
<script lang="ts">
import { superForm, formFieldProxy } from 'sveltekit-superforms';
let { data } = $props();
const superform = superForm(data.form); // The whole superForm object is required
const { form } = superform; // Deconstruct as usual here
const { path, value, errors, constraints, tainted } = formFieldProxy(superform, 'name');
</script>
```
**Options:**
```ts
{
taint?: boolean | 'untaint' | 'untaint-all';
}
```
The option can be used to prevent tainting the form when modifying the proxy.
For more details about formFieldProxy, see the [components page](/components#using-a-formfieldproxy).
### arrayProxy(superForm, fieldName, options)
Proxies an array in a form, returning stores similar to `superForm` but for the array.
> The whole object returned from `superForm` is required here, not just the `$form` store.
```svelte
<script lang="ts">
import { superForm, arrayProxy } from 'sveltekit-superforms';
let { data } = $props();
const superform = superForm(data.form); // The whole superForm object is required
const { form } = superform; // Deconstruct as usual here
const { path, values, errors, valueErrors } = arrayProxy(superform, 'tags');
</script>
```
- `errors` displays errors for the *array itself*, for example if the number of items are too few.
- `valueErrors` is an array that lists errors for the *content* of the array.
**Options:**
```ts
{
taint?: boolean | 'untaint' | 'untaint-all';
}
```
The option can be used to prevent tainting the form when modifying the proxy.
An example of how to use `arrayProxy` in a component is available [on Stackblitz](https://stackblitz.com/edit/sveltekit-superforms-multi-select?file=src%2Froutes%2F%2Bpage.svelte,src%2Froutes%2FMultiSelect.svelte).
### fieldProxy(object, fieldName)
Proxies field access in any object, usually in `$form`, but in that case `formFieldProxy` and `arrayProxy` are more convenient.
```svelte
<script lang="ts">
import { superForm, fieldProxy } from 'sveltekit-superforms';
let { data } = $props();
const { form } = superForm(data.form);
// Proxy any field in an object
const nameProxy = fieldProxy(form, 'name');
</script>
```
**Options:**
```ts
{
taint?: boolean | 'untaint' | 'untaint-all';
}
```
## Proxy example
Given the following schema:
```ts
const schema = z.object({
date: z.date().optional()
});
```
A proxy for a [HTML date field](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date) can be used like this:
```svelte
<script lang="ts">
import { superForm, dateProxy } from 'sveltekit-superforms';
import type { PageData } from './$types.js';
let { data } = $props();
const { form, enhance } = superForm(data.form);
const date = dateProxy(form, 'date', { format: 'date', empty: 'undefined' });
</script>
<input name="date" type="date" bind:value={$date} />
```
## Components
### SuperDebug
`SuperDebug` is a must-have debugging component that gives you colorized and nicely formatted output for any data structure, usually `$form`.
More information and usage examples can be found [on the SuperDebug page](/super-debug).
---
File: /src/routes/components/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
<Head title="Forms and fields in components" />
# Forms and fields in components
Looking at the rather simple [get started tutorial](/get-started), it's obvious that quite a bit of boilerplate code adds up for a Superform:
```svelte
<!-- For each form field -->
<label for="name">Name</label>
<input
type="text"
name="name"
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
{...$constraints.name}
/>
{#if $errors.name}
<span class="invalid">{$errors.name}</span>
{/if}
```
And it also gets bad in the script part when you have more than a couple of forms on the page:
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms'
let { data } = $props();
const {
form: loginForm,
errors: loginErrors,
enhance: loginEnhance,
//...
} = superForm(data.loginForm);
const {
form: registerForm,
errors: registerErrors,
enhance: registerEnhance,
// ...
} = superForm(data.registerForm);
</script>
```
This leads to the question of whether a form and its fields can be factored out into components?
## Factoring out a form
To do this, you need the type of the schema, which can be defined as follows:
**src/lib/schemas.ts**
```ts
export const loginSchema = z.object({
email: z.string().email(),
password: // ...
});
export type LoginSchema = typeof loginSchema;
```
Now you can import and use this type in a separate component:
**src/routes/LoginForm.svelte**
```svelte
<script lang="ts">
import type { SuperValidated, Infer } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms'
import type { LoginSchema } from '$lib/schemas';
let { data } : { data : SuperValidated<Infer<LoginSchema>> } = $props();
const { form, errors, enhance } = superForm(data);
</script>
<form method="POST" use:enhance>
<!-- Business as usual -->
</form>
```
[SuperValidated](https://superforms.rocks/api#supervalidate-return-type) is the return type from [superValidate](https://superforms.rocks/api#supervalidateadapter--data-adapter--options-options), which we called in the load function.
This component can now be passed the `SuperValidated` form data (from the `PageData` we returned from `+page.server.ts`), making the page much less cluttered:
**+page.svelte**
```svelte
<script lang="ts">
let { data } = $props();
</script>
<LoginForm data={data.loginForm} />
<RegisterForm data={data.registerForm} />
```
If your schema input and output types differ, or you have a strongly typed [status message](/concepts/messages#strongly-typed-message), you can add two additional type parameters:
```svelte
<script lang="ts">
import type { SuperValidated, Infer, InferIn } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms'
import type { LoginSchema } from '$lib/schemas';
let { data } : {
data : SuperValidated<Infer<LoginSchema>, { status: number, text: string }, InferIn<LoginSchema>>
} = $props();
const { form, errors, enhance, message } = superForm(data);
</script>
{#if $message.text}
...
{/if}
<form method="POST" use:enhance>
<!-- Business as usual -->
</form>
```
## Factoring out form fields
Since `bind` is available on Svelte components, we can make a `TextInput` component quite easily:
**TextInput.svelte**
```svelte
<script lang="ts">
import type { InputConstraint } from 'sveltekit-superforms';
let {
name,
value = $bindable(),
type = "text",
label,
errors,
constraints,
...rest
} : {
name: string;
value: string;
type?: string;
label?: string;
errors?: string[];
constraints?: InputConstraint;
} = $props();
</script>
<label>
{#if label}<span>{label}</span><br />{/if}
<input
{name}
{type}
bind:value
aria-invalid={errors ? 'true' : undefined}
{...constraints}
{...rest}
/>
</label>
{#if errors}<span class="invalid">{errors}</span>{/if}
```
**+page.svelte**
```svelte
<form method="POST" use:enhance>
<TextInput
name="name"
label="name"
bind:value={$form.name}
errors={$errors.name}
constraints={$constraints.name}
/>
<h4>Tags</h4>
{#each $form.tags as _, i}
<TextInput
name="tags"
label="Name"
bind:value={$form.tags[i].name}
errors={$errors.tags?.[i]?.name}
constraints={$constraints.tags?.name}
/>
{/each}
</form>
```
(Note that you must bind directly to `$form.tags` with the index, you cannot use the each loop variable, hence the underscore.)
This is a bit better and will certainly help when the components require some styling, but there are still plenty of attributes. Can we do even better?
> Things will get a bit advanced from here, so an alternative is to use the [Formsnap](/formsnap) library, which simplifies componentization a lot.
### Using a fieldProxy
You may have seen [proxy objects](/concepts/proxy-objects) being used for converting an input field string like `"2023-04-12"` into a `Date`, but that's a special usage of proxies. They can actually be used for any part of the form data, to have a store that can modify a part of the `$form` store. If you want to update just `$form.name`, for example:
```svelte
<script lang="ts">
import { superForm, fieldProxy } from 'sveltekit-superforms/client';
let { data } = $props();
const { form } = superForm(data.form);
const name = fieldProxy(form, 'name');
</script>
<div>Name: {$name}</div>
<button on:click={() => ($name = '')}>Clear name</button>
```
Any updates to `$name` will reflect in `$form.name`, and vice versa. Note that this will also [taint](/concepts/tainted) that field, so if this is not intended, you can use the whole superForm object and add an option:
```ts
const superform = superForm(data.form);
const { form } = superform;
const name = fieldProxy(superform, 'name', { taint: false });
```
A `fieldProxy` isn't enough here, however. We'd still have to make proxies for `form`, `errors`, and `constraints`, resulting in the same problem again.
### Using a formFieldProxy
The solution is to use a `formFieldProxy`, which is a helper function for producing the above proxies from a form. To do this, we cannot immediately deconstruct what we need from `superForm`, since `formFieldProxy` takes the form itself as an argument:
```svelte
<script lang="ts">
import type { PageData } from './$types.js';
import { superForm, formFieldProxy } from 'sveltekit-superforms/client';
let { data } : { data: PageData } = $props();
const superform = superForm(data.form);
const { path, value, errors, constraints } = formFieldProxy(superform, 'name');
</script>
```
But we didn't want to pass all those proxies, so let's imagine a component that will handle even the above proxy creation for us.
## A typesafe, generic component
```svelte
<TextField {superform} field="name" />
```
How nice would this be? This can actually be pulled off in a typesafe way with a bit of Svelte magic:
```svelte
<script lang="ts" context="module">
type T = Record<string, unknown>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>">
import type { HTMLInputAttributes } from 'svelte/elements';
import { formFieldProxy, type SuperForm, type FormPathLeaves } from 'sveltekit-superforms';
type Props = HTMLInputAttributes & {
superform: SuperForm<T>;
field: FormPathLeaves<T>;
};
let { superform, field, ...rest } : Props = $props();
const { value, errors, constraints } = formFieldProxy(superform, field);
</script>
<label>
{field}<br />
<input
name={field}
type="text"
aria-invalid={$errors ? 'true' : undefined}
bind:value={$value}
{...$constraints}
{...rest} />
</label>
{#if $errors}<span class="invalid">{$errors}</span>{/if}
```
The Svelte syntax requires `Record<string, unknown>` to be defined before its used in the `generics` attribute, so we have to import it in a module context. Now when `T` is defined (the schema object type), we can use it in the `form` prop to ensure that only a `SuperForm` matching the `field` prop is used.
> The `FormPathLeaves` type prevents using a field that isn't at the end of the schema (the "leaves" of the schema tree). This means that arrays and objects cannot be used in `formFieldProxy`. Array-level errors are handled [like this](/concepts/error-handling#form-level-and-array-errors).
## Type narrowing for paths
Checkboxes don't bind with `bind:value` but with `bind:checked`, which requires a `boolean`.
Because our component is generic, `value` returned from `formFieldProxy` is unknown, but we need a `boolean` here. Then we can add a type parameter to `FormPathLeaves` to narrow it down to a specific type, and use the [satisfies](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) operator to specify the type:
```svelte
<script lang="ts" context="module">
type T = Record<string, unknown>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>">
import {
formFieldProxy, type FormFieldProxy,
type SuperForm, type FormPathLeaves
} from 'sveltekit-superforms';
let { superForm, field, ...rest } : { superform: SuperForm<T>, field: FormPathLeaves<T, boolean> } = $props();
const { value, errors, constraints } = formFieldProxy(superform, field) satisfies FormFieldProxy<boolean>;
</script>
<input
name={field}
type="checkbox"
class="checkbox"
bind:checked={$value}
{...$constraints}
{...rest}
/>
```
This will also narrow the `field` prop, so only `boolean` fields in the schema can be selected when using the component.
Checkboxes, especially grouped ones, can be tricky to handle. Read the Svelte tutorial about [bind:group](https://svelte.dev/tutorial/group-inputs), and see the [Ice cream example](https://stackblitz.com/edit/superforms-2-group-inputs?file=src%2Froutes%2F%2Bpage.server.ts,src%2Froutes%2F%2Bpage.svelte) on Stackblitz if you're having trouble with it.
## Using the componentized field in awesome ways
Using this component is now as simple as:
```svelte
<TextField {superform} field="name" />
```
But to show off some super proxy power, let's recreate the tags example above with the `TextField` component:
```svelte
<form method="POST" use:enhance>
<TextField name="name" {superform} field="name" />
<h4>Tags</h4>
{#each $form.tags as _, i}
<TextField name="tags" {superform} field="tags[{i}].name" />
{/each}
</form>
```
We can now produce a type-safe text field for any object inside our data, which will update the `$form` store, and to add new tags, just append a tag object to the tags array:
```ts
$form.tags = [...$form.tags, { id: undefined, name: '' }];
```
In general, nested data requires the `dataType` option to be set to `'json'`, except arrays of primitive values, which are [coerced automatically](/concepts/nested-data#an-exception-arrays-with-primitive-values).
I hope you now feel under your fingers the superpowers that Superforms bring! 💥
---
File: /src/routes/concepts/client-validation/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Client-side validation
<Head title="Client-side validation" />
There are two client-side validation options with Superforms:
* The built-in browser validation, which doesn't require JavaScript to be enabled in the browser.
* Using a validation schema, usually the same one as on the server. Requires JavaScript and [use:enhance](/concepts/enhance).
## Built-in browser validation
There is a web standard for [form input](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation), which doesn't require JavaScript and is virtually effortless to use with Superforms:
### constraints
To use the built-in browser validation, just spread the `$constraints` store for a schema field on its input field:
```svelte
<script lang="ts">
let { data } = $props();
const { form, constraints } = superForm(data.form);
</script>
<input
name="email"
type="email"
bind:value={$form.email}
{...$constraints.email} />
```
The constraints is an object with validation properties mapped from the schema:
```ts
{
pattern?: string; // The *first* string validator with a RegExp pattern
step?: number | 'any'; // number with a step validator
minlength?: number; // string with a minimum length
maxlength?: number; // string with a maximum length
min?: number | string; // number if number validator, ISO date string if date validator
max?: number | string; // number if number validator, ISO date string if date validator
required?: true; // true if not nullable, nullish or optional
}
```
#### Special input formats
For some input types, a certain format is required. For example with `date` fields, both the underlying data and the constraint needs to be in `yyyy-mm-dd` format, which can be handled by [using a proxy](/concepts/proxy-objects#date-input-issues) and adding attributes after the constraints spread, in which case they will take precedence:
```svelte
<input
name="date"
type="date"
aria-invalid={$errors.date ? 'true' : undefined}
bind:value={$proxyDate}
{...$constraints.date}
min={$constraints.date?.min?.toString().slice(0, 10)}
/>
```
Check the validation attributes and their valid values at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#validation-related_attributes).
## Using a validation schema
The built-in browser validation can be a bit constrained; for example, you can't easily control the position and appearance of the error messages. Instead (or supplementary), you can use a validation schema and customize the validation with a number of options, so the form errors will be displayed in real-time.
> As with most client-side functionality, [use:enhance](/concepts/enhance) is required for real-time validation.
```ts
const { form, enhance, constraints, validate, validateForm } = superForm(data.form, {
validators: ClientValidationAdapter<S> | 'clear' | false,
validationMethod: 'auto' | 'oninput' | 'onblur' | 'onsubmit' = 'auto',
customValidity: boolean = false
})
```
### validators
```ts
validators: ClientValidationAdapter<S> | 'clear' | false
```
Setting the `validators` option to an adapter with the same schema as on the server, is the most convenient and recommended way. Just put the schema in a shared directory, `$lib/schemas` for example, and import it on the client as well as on the server.
Adding a adapter on the client will increase the client bundle size a bit, since the validation library now has to be imported there too. But the client-side adapter is optimized to be as small as possible, so it shouldn't be too much of an issue. To use it, append `Client` to the adapter import, for example:
```ts
import { valibotClient } from 'sveltekit-superforms/adapters';
import { schema } from './schema.js';
const { form, errors, enhance } = superForm(data.form, {
validators: valibotClient(schema)
});
```
> This works only with the same schema as the one used on the server. If you need to switch schemas on the client, you need the full adapter (for example `zod` instead of `zodClient`).
As a super-simple alternative to an adapter, you can also set the option to `'clear'`, to remove errors for a field as soon as it's modified.
#### Switching schemas
You can even switch schemas dynamically, with the `options` object. `options.validators` accepts a partial validator, which can be very useful for multi-step forms:
```ts
import { valibot } from 'sveltekit-superforms/adapters';
import { schema, partialSchema } from './schema.js';
const { form, errors, enhance, options } = superForm(data.form, {
// Validate the first step of the form
validators: valibot(partialSchema)
});
// When moving to the last step of the form
options.validators = valibot(schema)
```
As mentioned, you need the full adapter to switch schemas dynamically. An exception will be thrown if a client adapter is detected different from the initial `validators` option.
### validationMethod
```ts
validationMethod: 'auto' | 'oninput' | 'onblur' | 'onsubmit',
```
The validation is triggered when **a value is changed**, not just when focusing on, or tabbing through a field. The default validation method is based on the "reward early, validate late" pattern, [a researched way](https://medium.com/wdstack/inline-validation-in-forms-designing-the-experience-123fb34088ce) of validating input data that makes for a high user satisfaction:
- If entering data in a field that has or previously had errors, validate on `input`
- Otherwise, validate on `blur`.
But you can instead use the `oninput` or `onblur` settings to always validate on one of these respective events, or `onsubmit` to validate only on submit.
> Be aware that the whole schema will be validated, not just the modified field, because errors can be added to any field in the schema during validation with Zod's [refine](https://zod.dev/?id=customize-error-path) or similar, so the whole schema must be validated to know the final result.
### customValidity
This option uses the browser built-in tooltip to display validation errors, so neither `$errors` nor `$constraints` are required on the form. See the [error handling page](/concepts/error-handling#customvalidity) for details and an example.
### validate
The `validate` function, deconstructed from `superForm`, gives you complete control over the validation process for specific fields. Examples of how to use it:
```ts
const { form, enhance, validate } = superForm(data.form, {
validators: zod(schema) // Required option for validate to work
});
// Simplest case, validate what's in the field right now
validate('name');
// Validate without updating, for error checking
const nameErrors = await validate('name', { update: false });
// Validate and update field with a custom value
validate('name', { value: 'Test' });
// Validate a custom value, update errors only
validate('name', { value: 'Test', update: 'errors' });
// Validate and update nested data, and also taint the field
validate('tags[1].name', { value: 'Test', taint: true });
```
### validateForm
Similar to `validate`, `validateForm` lets you validate the whole form and return a `SuperValidated` result:
```ts
const { form, enhance, validateForm } = superForm(data.form, {
validators: zod(schema) // Required option for validate to work
});
const result = await validateForm();
if (result.valid) {
// ...
}
// You can use the update option to trigger a client-side validation
await validateForm({ update: true });
// Or the schema option to validate the form partially
const result2 = await validateForm({ schema: zod(partialSchema) });
```
## Asynchronous validation and debouncing
The validation is asynchronous, but a slow validator will delay the final result, so for a server round-trip validation, like checking if a username is available, you might want to consider a [SPA action form](/concepts/spa#spa-action-form), or handle it with `on:input` and a package like [throttle-debounce](https://www.npmjs.com/package/throttle-debounce). Errors can be set manually by updating the `$errors` store:
```ts
// Needs to be a string[]
$errors.username = ['Username is already taken.'];
```
## Test it out
This example demonstrates how validators are used to check if tags are of the correct length.
Set a tag name to blank and see that no errors show up until you move focus outside the field (blur). When you go back and correct the mistake, the error is removed as soon as you enter more than one character (input).
<Form {data} />
<Next section={concepts} />
---
File: /src/routes/concepts/enhance/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Next from '$lib/Next.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Progressive enhancement
<Head title="Progressive enhancement with use:enhance" />
By using `enhance` returned from `superForm`, we'll get the client-side enhancement expected from a modern website:
```svelte
<script lang="ts">
const { form, enhance } = superForm(data.form);
// ^^^^^^^
</script>
<form method="POST" use:enhance>
```
The `use:enhance` action takes no arguments; instead, events are used to hook into the default SvelteKit use:enhance parameters and more. Check out the [events page](/concepts/events) for details.
> Without `use:enhance` (and JavaScript enabled in the browser), the form will be static. No events, loading timers, auto-focus, etc. The only things that will work are [constraints](/concepts/client-validation#constraints). Also note that SvelteKit's own `use:enhance` cannot be used; you must use the one returned from `superForm`, and it should only be used on a single form element - you cannot share it between forms.
## Modifying the use:enhance behavior
The default `use:enhance` behavior can be modified, there are three options available, here shown with the default values; you don't need to add them unless you want to change a value.
```ts
const { form, enhance, reset } = superForm(data.form, {
applyAction: true,
invalidateAll: true | 'force',
resetForm: true
});
```
### applyAction
When `applyAction` is `true`, the form will have the default SvelteKit behavior of both updating and reacting on `$page.form` and `$page.status`, and also redirecting automatically.
Turning this behavior off with `false` can be useful when you want to isolate the form from other sources updating the page, for example Supabase events, a known source of confusing form behavior. Read more about `applyAction` [in the SvelteKit docs](https://svelte.dev/docs/kit/form-actions#Progressive-enhancement-Customising-use:enhance).
In rare cases you may want the form to not react on page reloads, for example if you call [invalidateAll](https://svelte.dev/tutorial/kit/invalidate-all) but want to avoid the form to revert to its initial state. Then you can specify `applyAction: 'never'`, and nothing but submitting the form will alter its state.
### invalidateAll
When `invalidateAll` is `true` (the default) and a successful validation result is returned from the server, the page will be invalidated and the load functions will run again. A login form takes advantage of this to update user information on the page, but the default setting may cause problems with [multiple forms on the same page](/concepts/multiple-forms), since the load function will reload the data for all forms defined there.
#### Optimistic updates
The data returned in the form action can conflict with the new data from the load function. The form action update is "optimistic", meaning that what's returned there will be displayed, assuming that all data was supposed to be updated. But if you update the form partially, the form data will be out of sync with the load function data, in which case you may want to wait for the load function data. This can be achieved with by setting `invalidateAll: 'pessimistic'`. Now the load function data will be prioritized, and the `reset` function will also use the latest load function data when called.
### resetForm
When `true`, reset the form upon a successful validation result.
Note however, that since we're using `bind:value` on the input fields, a HTML form reset (clearing all fields in the DOM) won't have any effect. So in Superforms, **resetting means going back to the initial state of the form data**, basically setting `$form` to what was initially sent to `superForm`.
For a custom reset, you can instead modify the `data` field returned from `superValidate` on the server, or use the [events](/concepts/events) together with the [reset](/api#superform-return-type) function on the client.
## When to change the defaults?
Quite rarely! If you have a single form on the page and nothing else is causing the page to invalidate, you'll probably be fine as it is. For multiple forms on the same page, you have to experiment with these three options. Read more on the [multiple forms](/concepts/multiple-forms) page.
## Making the form behave like the SvelteKit default
Any [ActionResult](https://kit.svelte.dev/docs/types#public-types-actionresult) with status `error` is transformed into `failure` by Superforms to avoid form data loss. The SvelteKit default is to render the nearest `+error.svelte` page, which will wipe out the form and all data that was just entered. Returning `fail` with a [status message](/concepts/messages) or using the [onError event](/concepts/events#onerror) is a more user-friendly way of handling server errors.
You can prevent this by setting the following option. Use with care, since the purpose of the change is to protect the user from data loss.
```ts
const { form, enhance } = superForm(data.form, {
// On ActionResult error, render the nearest +error.svelte page
onError: 'apply',
});
```
<Next section={concepts} />
---
File: /src/routes/concepts/error-handling/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import CustomValidity from './CustomValidity.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Error handling
<Head title="Error handling" />
By deconstructing `errors` from `superForm`, you'll get an object with form errors that you can display where it's appropriate:
```svelte
<script lang="ts">
const { form, errors } = superForm(data.form);
</script>
<form method="POST">
<label for="name">Name</label>
<input
name="name"
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
/>
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
<div><button>Submit</button></div>
</form>
```
The `aria-invalid` attribute is used to automatically focus on the first error field; see the [errorSelector](/concepts/error-handling#errorselector) option further below.
## setError
Most errors will be set automatically when the data is validated, but you may want to add errors after determining that the data is valid. This is easily done with the `setError` helper function.
```ts
import { setError, superValidate, fail } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(schema));
if (!form.valid) {
return fail(400, { form });
}
if (db.users.find({ where: { email: form.data.email } })) {
return setError(form, 'email', 'E-mail already exists.');
}
return { form };
}
};
```
`setError` returns a `fail(400, { form })` so it can be returned immediately, or more errors can be added by calling it multiple times before returning. Check [the API](/api#seterrorform-field-error-options) for additional options.
If you're using [nested data](/concepts/nested-data), a string path is used to specify where in the data structure the error is:
```ts
setError(form, `post.tags[${i}].name`, 'Invalid tag name.');
```
> Errors added with `setError` will be removed when [client-side validation](/concepts/client-validation) is used, and the first client validation occurs (such as modifying a field).
## Server errors
In the case of a server error, Superforms will normalize the different kind of server errors that can occur:
| Error type | Example |
| ---------- | --------- |
| [Expected error](https://kit.svelte.dev/docs/errors#expected-errors) | `error(404, { code: 'user_not_found', message: 'Not found' })` |
| Exception | `throw new Error("Database connection error")` |
| JSON response | `return json({ code: 'rate_limited', status: 429 }, { status: 429 })` |
| Other response | `<!doctype html><html lang="en"><head><meta charset=...` |
These should be handled with the [onError](/concepts/events#onerror) event, assuming the Superforms [use:enhance](/concepts/enhance) action is applied to the form. If it isn't, the nearest `+error.svelte` page will be rendered.
> Without an [onError](/concepts/events#onerror) event, errors will throw an exception in the browser. Read the link for more information (and how to ignore errors).
In general, returning a [status message](/concepts/messages) is recommended instead of calling `error` or throwing exceptions, as this will make even the non-JS users keep their form data.
## Initial form errors
The default data in an empty form is usually invalid, but displaying lots of errors upon page load doesn't look good. Superforms handles it like this:
If no data was posted or sent to `superValidate`, **no errors will be returned** unless the `errors` option in `superValidate` is `true`. This is what happens in load functions when the only the schema is sent to `superValidate`:
```ts
export const load = async () => {
// No errors set, since no data is sent to superValidate
const form = await superValidate(zod(schema));
// No data, but errors can still be added with an option
const form2 = await superValidate(zod(schema), { errors: true });
};
```
If data was sent to `superValidate`, either posted or populated with data, **errors will be returned** unless the `errors` option is `false`.
```ts
export const load = async () => {
const initialData = { test: 123 };
// Form is populated, so errors will be set if validation fails
const form = await superValidate(initialData, zod(schema));
// No errors will be set, even if validation fails
const form2 = await superValidate(initialData, zod(schema), { errors: false });
};
export const actions = {
default: async ({ request }) => {
// Data is posted, so form.errors will be populated
const form = await superValidate(request, zod(schema));
// Unless we turn them off (which is rare in form actions)
const form2 = await superValidate(request, zod(schema), { errors: false });
}
};
```
> The `errors` option does not affect the `valid` property of the `SuperValidated` object, which always indicates whether validation succeeded or not.
## Usage (client)
As said, errors are available in the `$errors` store. It gives you high flexibility, since you can place error messages anywhere on the page.
In larger forms, the submit button may be far away from the error, so it's nice to show the user where the first error is. There are a couple of options for that:
```ts
const { form, enhance, errors, allErrors } = superForm(data.form, {
errorSelector: string | undefined = '[aria-invalid="true"],[data-invalid]',
scrollToError: 'auto' | 'smooth' | 'off' | boolean | ScrollIntoViewOptions = 'smooth',
autoFocusOnError: boolean | 'detect' = 'detect',
stickyNavbar: string | undefined = undefined,
customValidity: boolean = false
})
```
### errorSelector
This is the CSS selector used to locate the invalid input fields after form submission. The default is `[aria-invalid="true"],[data-invalid]`, and the first one found in the form will be scrolled to and focused on, depending on the other settings. You usually set it on the input fields as such:
```svelte
<input
type="email"
name="email"
bind:value={$form.email}
aria-invalid={$errors.email ? 'true' : undefined} />
```
### scrollToError
The `scrollToError` option determines how to scroll to the first error message in the form. `smooth` and `auto` are values from [Window.scroll](https://developer.mozilla.org/en-US/docs/Web/API/Window/scroll). If the non-string options are used, [Element.scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) will be called with the option. This is mostly used with nested scrollbars, in which case Window.scroll won't work.
### autoFocusOnError
When `autoFocusOnError` is set to its default value `detect`, it checks if the user is on a mobile device; **if not**, it will automatically focus on the first error input field. It's prevented on mobile devices since focusing will open the on-screen keyboard, causing the viewport to shift, which could hide the validation error.
### stickyNavbar
If you have a sticky navbar, set its CSS selector here and it won't hide any errors due to its height and z-index.
### customValidity
This option uses the [Constraint Validation API](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api) to display validation errors. By enabling this, with [use:enhance](/concepts/enhance) added to the form, instead of the standard messages, the Zod validation errors will now be displayed in the browser validation tooltip. Submit the following form without entering any data to see it in action:
<CustomValidity {data} />
Since validation is handled by Superforms, there is no need for spreading `$constraints` on the field. But the biggest win is that there is no need to display `$errors`, making for a minimal html:
```ts
const { form, enhance } = superForm(data.form, {
customValidity: true,
// Not required, but will use client-side validation for real-time error display:
validators: schema
});
```
```svelte
<input type="text" name="name" bind:value={$form.name} />
```
The `name` attribute is required on the input fields. If you want to exclude a field from displaying the tooltip, add a `data-no-custom-validity` attribute to it.
> Just be aware that since `use:enhance` is needed, `customValidity` requires JavaScript to be enabled, unlike browser [constraints](/concepts/client-validation#built-in-browser-validation).
## Form-level and array errors
It's possible to set form-level errors by refining the schema, which works better together with [client-side validation](/concepts/client-validation), as errors added with [setError](/api#seterrorform-field-error-options) won't persist longer than the first validation of the schema on the client.
```ts
const refined = z.object({
tags: z.string().array().max(3)
password: z.string().min(8),
confirm: z.string().min(8)
})
.refine((data) => data.password == data.confirm, "Passwords didn't match.");
```
These can be accessed on the client through `$errors?._errors`. The same goes for array errors, which in the above case can be accessed through `$errors.tags?._errors` (alternatively, use an [arrayProxy](/api#arrayproxysuperform-fieldname-options)).
### Setting field errors with refine
You may want to set the error on the password or the confirm field instead of a form-level error. In that case you can add a path to the Zod [refine](https://zod.dev/?id=refine) option:
```ts
const refined = z.object({
tags: z.string().array().max(3)
password: z.string().min(8),
confirm: z.string().min(8)
})
.refine((data) => data.password == data.confirm, {
message: "Passwords didn't match",
path: ["confirm"]
});
```
For nested data, use multiple elements like `["user", "email"]`, which corresponds to `user.email` in the schema.
As said, setting errors on the schema like this is preferred, but it may not always be possible. When you need to set errors after validation, use the [setError](/api#seterrorform-field-error-options) function.
> If you would like a message to persist until the next form submission regardless of validation, use a [status message](/concepts/messages) instead.
## Listing errors
You may also want to list the errors above the form. The `$allErrors` store can be used for this. It's an array that contains all errors and their field names:
```svelte
{#if $allErrors.length}
<ul>
{#each $allErrors as error}
<li>
<b>{error.path}:</b>
{error.messages.join('. ')}
</li>
{/each}
</ul>
{/if}
```
`$allErrors.length` can also be useful to disable the submit button if there are any errors.
## Customizing error messages in the schema
Most methods in the validation schema has a parameter for a custom error message, so you can just add them there. For example with Zod:
```ts
const schema = z.object({
name: z.string().min(2, "Name is too short."),
email: z.string().email("That's a strange email.")
});
```
This is also a good place for translation strings.
## Test it out
This form has `aria-invalid` set on erroneous fields, and lists all errors on top of the form using `$allErrors`. Try to submit and see that the first error field gets focus automatically (unless on mobile).
<Form {data} />
<Next section={concepts} />
---
File: /src/routes/concepts/events/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import Flowchart from './Flowchart.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
</script>
# Events
<Head title="Events" />
A number of events give you full control over the submit process. They are triggered every time the form is submitted.
> Events are only available when JavaScript is enabled in the browser and the custom [use:enhance](/concepts/enhance) is added to the form.
## Event flowchart
<Flowchart />
> In a [single-page application](/concepts/spa), or if [client-side validation](/concepts/client-validation) fails, the validation happens entirely on the client, instead of being returned from the server between `onSubmit` and `onResult`.
## Usage
```ts
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
```ts
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](https://kit.svelte.dev/docs/types#public-types-submitfunction) signature.
There are also three extra properties in the Superforms `onSubmit` event, for more advanced scenarios:
#### jsonData
If you're using [nested data](/concepts/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:
```ts
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](/concepts/client-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](/concepts/spa) 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.
```ts
import { superForm } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { customSchema } from './schemas.js';
let step = $state(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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/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.
```ts
import { superForm } from 'sveltekit-superforms';
import type { SubmitFunction } from '@sveltejs/kit';
let progress = $state(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
```ts
onResult: ({ result, formElement, cancel }) => void
```
If the submission isn't cancelled and [client-side validation](/concepts/client-validation) succeeds, the form is posted to the server. It then responds with a SvelteKit [ActionResult](https://kit.svelte.dev/docs/types#public-types-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.
> In most cases, you don't have to care about the `ActionResult`. To check if the form validation succeeded, use [onUpdated](/concepts/events#onupdated), or [onUpdate](/concepts/events#onupdate) if you want to modify or cancel the result before it's displayed.
#### 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:
```ts
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
```ts
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](/concepts/spa) page for more details.
> The `form` parameter is deliberately named "form", to avoid using the `$form` store, as changes to the form parameter are applied to `$form` and the other stores, when the event is completed.
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:
```ts
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
```ts
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**
```ts
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
```ts
onError: (({ result }) => void) | 'apply'
```
If you use the SvelteKit [error](https://kit.svelte.dev/docs/errors#expected-errors) function on the server, you should use the `onError` event to catch it.
Depending on what kind of error occurs, `result.error` will have a different shape:
| Error type | Shape |
| ---------- | ----- |
| [Expected error](https://kit.svelte.dev/docs/errors#expected-errors) | `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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)), 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:
```ts
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](https://kit.svelte.dev/docs/errors#unexpected-errors).
> Without an [onError](/concepts/events#onerror) event, errors will throw an exception in the browser. If you want to ignore errors, to allow loading timers to finish for example, you can use a noop, which works as an empty try/catch block: `onError: () => {}`
You can also set `onError` to the string value `'apply'`, in which case the default SvelteKit error behavior will be used, which is to render the nearest [+error](https://kit.svelte.dev/docs/routing#error) page, also wiping out the form. To avoid data loss even for non-JavaScript users, returning a [status message](/concepts/messages) 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:
```ts
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.
`onChange` is useful for deep reactivity, as you have the full path to the changes in the event.
<Next section={concepts} />
---
File: /src/routes/concepts/files/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Next from '$lib/Next.svelte'
import Examples from './Examples.svelte'
import { concepts } from '$lib/navigation/sections'
</script>
# File uploads
<Head title="File upload and validation" />
From version 2, Superforms has full support for file uploads. For that, you need a schema that can validate files. Zod has this possibility:
```ts
// NOTE: Import fail from Superforms, not from @sveltejs/kit!
import { superValidate, fail, message } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
const schema = z.object({
image: z
.instanceof(File, { message: 'Please upload a file.'})
.refine((f) => f.size < 100_000, 'Max 100 kB upload size.')
});
export const load = async () => {
return {
form: await superValidate(zod(schema))
}
};
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(schema));
if (!form.valid) {
return fail(400, { form });
}
// TODO: Do something with the image
console.log(form.data.image);
return message(form, 'You have uploaded a valid file!');
}
};
```
## Returning files in form actions
**Important:** Because file objects cannot be serialized, you must return the form using `fail`, `message` or `setError` imported from Superforms:
```ts
import { message, setError, fail } from 'sveltekit-superforms';
// message, setError and the custom fail handles this automatically:
if (!form.valid) return fail(400, { form });
return message(form, 'Posted OK!');
return setError(form, 'image', 'Could not process file.');
```
Otherwise a `withFiles` helper function is required, which is not recommended:
```ts
// Importing the default fail:
import { fail } from '@sveltejs/kit';
// Prefer to import message, setError and fail from here instead:
import { withFiles } from 'sveltekit-superforms';
// With the @sveltejs/kit fail:
if (!form.valid) {
return fail(400, withFiles({ form }));
}
// When returning just the form
return withFiles({ form })
```
This will remove the file objects from the form data, so SvelteKit can serialize it properly.
## Examples
The recommended way to bind the file input to the form data is through a `fileProxy` or `filesProxy`, but you can also do it with an `on:input` handler. Here are examples for both, which also shows how to add client-side validation for files, which can save plenty of bandwidth!
> Remember that you need to add `enctype="multipart/form-data"` on the form element for file uploads to work.
### Single file input
```ts
export const schema = z.object({
image: z
.instanceof(File, { message: 'Please upload a file.'})
.refine((f) => f.size < 100_000, 'Max 100 kB upload size.')
});
```
<Examples>
<span slot="proxy">
```svelte
<script lang="ts">
import { superForm, fileProxy } from 'sveltekit-superforms'
import { zodClient } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
let { data } = $props();
const { form, enhance, errors } = superForm(data.form, {
validators: zodClient(schema)
})
const file = fileProxy(form, 'image')
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<input
type="file"
name="image"
accept="image/png, image/jpeg"
bind:files={$file}
/>
{#if $errors.image}<span>{$errors.image}</span>{/if}
<button>Submit</button>
</form>
```
</span>
<span slot="input">
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms'
import { zodClient } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
let { data } = $props();
const { form, enhance, errors } = superForm(data.form, {
validators: zodClient(schema)
})
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<input
type="file"
name="image"
accept="image/png, image/jpeg"
on:input={(e) => ($form.image = e.currentTarget.files?.item(0) as File)}
/>
{#if $errors.image}<span>{$errors.image}</span>{/if}
<button>Submit</button>
</form>
```
> The `as File` casting is needed since `null` is the value for "no file", so be aware that `$form.image` may be `null` even though the schema type says otherwise. If you want the upload to be optional, set the field to `nullable` and it will be type-safe.
</span>
</Examples>
### Multiple files
We need an array to validate multiple files:
```ts
const schema = z.object({
images: z
.instanceof(File, { message: 'Please upload a file.'})
.refine((f) => f.size < 100_000, 'Max 100 kB upload size.')
.array()
});
```
<Examples>
<span slot="proxy">
```svelte
<script lang="ts">
import { superForm, filesProxy } from 'sveltekit-superforms'
import { zodClient } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
let { data } = $props();
const { form, enhance, errors } = superForm(data.form, {
validators: zodClient(schema)
})
const files = filesProxy(form, 'images');
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<input
type="file"
multiple
name="images"
accept="image/png, image/jpeg"
bind:files={$files}
/>
{#if $errors.images}<span>{$errors.images}</span>{/if}
<button>Submit</button>
</form>
```
</span>
<span slot="input">
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms'
import { zodClient } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
let { data } = $props();
const { form, enhance, errors } = superForm(data.form, {
validators: zodClient(schema)
})
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<input
type="file"
multiple
name="images"
accept="image/png, image/jpeg"
on:input={(e) => ($form.images = Array.from(e.currentTarget.files ?? []))}
/>
{#if $errors.images}<span>{$errors.images}</span>{/if}
<button>Submit</button>
</form>
```
> As there is no `bind:files` attribute on the input field, note that it cannot be hidden with an `{#if}` block. Use css instead to hide it.
</span>
</Examples>
> To use the file proxies in a component, `fileFieldProxy` and `filesFieldProxy` are available as a complement to `formFieldProxy`.
## Validating files manually
If your validation library cannot validate files, you can obtain `FormData` from the request and extract the files from there, after validation:
```ts
import { message, fail } from 'sveltekit-superforms';
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const form = await superValidate(formData, zod(schema));
if (!form.valid) return fail(400, { form });
const image = formData.get('image');
if (image instanceof File) {
// Validate and process the image.
}
return message(form, 'Thank you for uploading an image!');
}
};
```
If the file field isn't a part of the schema, but you still want errors for it, you can add an optional field to the schema with the same name, and use [setError](/concepts/error-handling#seterror) to set and display an error message.
## Progress bar for uploads
If you'd like a progress bar for large file uploads, check out the [customRequest](/concepts/events#customrequest) option for the `onSubmit` event.
## Preventing file uploads
To prevent file uploads, set the `{ allowFiles: false }` option in `superValidate`. This will set all files to `undefined`, so you don't have to use `withFiles`.
This will also happen if you have migrated from version 1 and defined [SUPERFORMS_LEGACY](/migration-v2/#the-biggest-change-important). In that case, set `{ allowFiles: true }` to allow files.
<Next section={concepts} />
---
File: /src/routes/concepts/messages/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Status messages
<Head title="Status messages" />
A status message like "Form posted" can be displayed after submitting a form. The validation object contains a `message` store used for this:
## Usage
```ts
const { form, message } = superForm(data.form);
```
It is used to display the message on the client, like any other store:
```svelte
{#if $message}
<div class="message">{$message}</div>
{/if}
```
However, we need to send it from the server first. Using the `message` function makes this rather simple.
## The message helper
```ts
import { message, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(schema));
if (!form.valid) {
// Will return fail(400, { form }) since form isn't valid
return message(form, 'Invalid form');
}
if (form.data.email.includes('spam')) {
// Will also return fail, since status is >= 400
// form.valid will also be set to false.
return message(form, 'No spam please', {
status: 403
});
}
// Just returns { form } with the message (and status code 200).
return message(form, 'Valid form!');
}
};
```
You can return any type with the message, like an object, if you want to send more information than a string:
```ts
return message(form, { text: 'User created', id: newId })
```
See right below for how to make this data strongly typed.
> A successful form action in SvelteKit can only return status code `200`, so the status option for `message` must be in the range `400-599`, otherwise `{ form }` will be returned with a status of `200`, no matter what the status option is set to.
## Strongly typed message
The message is of type `any` by default, but you can type it using the `superValidate` type parameters:
```ts
import { type Infer, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
// Inferred schema type as first parameter, message type second
const form = await superValidate<Infer<typeof schema>, string>(event, zod(schema));
```
A string can be a bit limiting though; more realistically, there will be some kind of status for the form submission, so making a `Message` type can be useful for consistency.
```ts
import { type Infer, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
type Message = { status: 'error' | 'success' | 'warning'; text: string };
const form = await superValidate<Infer<typeof schema>, Message>(event, zod(schema));
```
To simplify this even further, if you have the same type for all status messages across the project, you can add a `Message` type to the `App.Superforms` namespace in src/app.d.ts, and it will be automatically set, no need for generic type parameters:
**src/app.d.ts**
```ts
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
namespace Superforms {
type Message = {
type: 'error' | 'success', text: string
}
}
}
}
```
**src/routes/+page.svelte**
```svelte
<script lang="ts">
let { data } = $props();
const { form, message } = superForm(data.form);
</script>
{#if $message}
<div
class:success={$message.status == 'success'}
class:error={$message.status == 'error'}
>
{$message.text}
</div>
{/if}
```
Though if you want to keep it simple with a string or the default `any`, you can use `$page.status` to style the message appropriately:
```svelte
<script lang="ts">
import { page } from '$app/stores';
let { data } = $props();
const { form, message } = superForm(data.form);
</script>
{#if $message}
<div
class:success={$page.status == 200}
class:error={$page.status >= 400}
>
{$message}
</div>
{/if}
```
### Using the message data programmatically
If you return data that you want to use programmatically instead of just displaying it, like in a toast message, you can do that in the [onUpdate](/concepts/events#onupdate) or [onUpdated](/concepts/events#onupdated) event:
```ts
return message(form, { status: 'success', text: 'All went well' });
```
```ts
const { form, enhance } = superForm(data.form, {
onUpdated({ form }) {
if (form.message) {
// Display the message using a toast library
toast(form.message.text, {
icon: form.message.status == 'success' ? '✅' : '❌'
});
}
}
});
```
The difference between the two events is that you can modify and cancel the update in `onUpdate`, compared to `onUpdated`, where the form data, errors, etc have already updated, making it best for non-store-related things like displaying a toast.
## Limitations
Since there is no form data returned when redirecting, in that case the message will be lost. It's quite common to redirect after a successful post, so the `message` property isn't a general solution for displaying status messages.
The library [sveltekit-flash-message](https://github.com/ciscoheat/sveltekit-flash-message) is a complete solution that works with redirects, however. It can be directly integrated into Superforms, [documented here](/flash-messages).
<Next section={concepts} />
---
File: /src/routes/concepts/multiple-forms/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Multiple forms on the same page
<Head title="Multiple forms on the same page" />
Since there is only one `$page` store per route, multiple forms on the same page, like a register and login form, can cause problems since both form actions will update `$page.form`, possibly affecting the other form.
With Superforms, multiple forms on the same page are handled automatically **if you are using `use:enhance`, and the forms have different schema types**. When using the same schema for multiple forms, you need to set the `id` option:
```ts
const form = await superValidate(zod(schema), {
id: string | undefined
});
```
By setting an id on the server, you'll ensure that only forms with the matching id on the client will react to the updates.
> "Different schema types" means "different fields and types", so just copying a schema and giving it a different variable name will still count as the same schema. The contents of the schemas have to differ.
Here's an example of how to handle a login and registration form on the same page:
**+page.server.ts**
```ts
import { z } from 'zod';
import { fail } from '@sveltejs/kit';
import { message, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import type { Actions, PageServerLoad } from './$types';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
confirmPassword: z.string().min(8)
});
export const load: PageServerLoad = async () => {
// Different schemas, no id required.
const loginForm = await superValidate(zod(loginSchema));
const registerForm = await superValidate(zod(registerSchema));
// Return them both
return { loginForm, registerForm };
};
export const actions = {
login: async ({ request }) => {
const loginForm = await superValidate(request, zod(loginSchema));
if (!loginForm.valid) return fail(400, { loginForm });
// TODO: Login user
return message(loginForm, 'Login form submitted');
},
register: async ({ request }) => {
const registerForm = await superValidate(request, zod(registerSchema));
if (!registerForm.valid) return fail(400, { registerForm });
// TODO: Register user
return message(registerForm, 'Register form submitted');
}
} satisfies Actions;
```
The code above uses [named form actions](https://kit.svelte.dev/docs/form-actions#named-actions) to determine which form was posted. On the client, you'll post to these different form actions for the respective form:
**+page.svelte**
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
let { data } = $props();
const { form, errors, enhance, message } = superForm(data.loginForm, {
resetForm: true
});
const {
form: registerForm,
errors: registerErrors,
enhance: registerEnhance,
message: registerMessage
} = superForm(data.registerForm, {
resetForm: true
});
</script>
{#if $message}<h3>{$message}</h3>{/if}
<form method="POST" action="?/login" use:enhance>
E-mail: <input name="email" type="email" bind:value={$form.email} />
Password:
<input name="password" type="password" bind:value={$form.password} />
<button>Submit</button>
</form>
<hr />
{#if $registerMessage}<h3>{$registerMessage}</h3>{/if}
<form method="POST" action="?/register" use:registerEnhance>
E-mail: <input name="email" type="email" bind:value={$registerForm.email} />
Password:
<input name="password" type="password" bind:value={$registerForm.password} />
Confirm password:
<input
name="confirmPassword"
type="password"
bind:value={$registerForm.confirmPassword} />
<button>Submit</button>
</form>
```
> Note that there is a separate `use:enhance` for each form - you cannot share the enhance action between forms.
The above works well with forms that posts to a dedicated form action. But for more dynamic scenarios, let's say a database table where rows can be edited, the form id should correspond to the row id, and you'd want to communicate to the server which id was sent. This can be done by modifying the `$formId` store, to let the server know what `id` was posted, and what it should respond with.
## Setting id on the client
On the client, the id is picked up automatically when you pass `data.form` to `superForm`, so in general, you don't have to add it on the client.
```ts
// Default behavior: The id is sent along with the form data
// sent from the load function.
const { form, enhance, formId } = superForm(data.loginForm);
// In advanced cases, you can set the id as an option
// and it will take precedence.
const { form, enhance, formId } = superForm(data.form, {
id: 'custom-id'
});
```
You can also change the id dynamically with the `$formId` store, or set it directly in the form with the following method:
### Without use:enhance
Multiple forms also work without `use:enhance`, though in this case you must add a hidden form field called `__superform_id` with the `$formId` value:
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
let { data } = $props();
const { form, errors, formId } = superForm(data.form);
</script>
<form method="POST" action="?/login">
<input type="hidden" name="__superform_id" bind:value={$formId} />
</form>
```
This is also required if you're changing schemas in a form action, as can happen in [multi-step forms](/examples#multi-step-forms).
## Returning multiple forms
If you have a use case where the data in one form should update another, you can return both forms in the form action: `return { loginForm, registerForm }`, but be aware that you may need `resetForm: false` on the second form, as it will reset and clear the updated changes, if it's valid and a successful response is returned.
## Hidden forms
Sometimes you want a fetch function for a form field or a list of items, for example checking if a username is valid while entering it, or deleting rows in a list of data. Instead of doing this manually with [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch), which cannot take advantage of Superforms' loading timers, events and other functionality, you can create a hidden form that does most of the work, with the convenience you get from `superForm`:
```ts
// First the visible form
const { form, errors, ... } = superForm(...);
// The the hidden form
const { submitting, submit } = superForm(
{ username: '' },
{
invalidateAll: false,
applyAction: false,
multipleSubmits: 'abort',
onSubmit({ cancel, formData }) {
// Using the visible form data
if (!$form.username) cancel();
formData.set('username', $form.username);
},
onUpdated({ form }) {
// Update the other form to show the error message
$errors.username = form.errors.username;
}
}
);
const checkUsername = debounce(300, submit);
```
Create a form action for it:
```ts
const usernameSchema = fullSchema.pick({ username: true });
export const actions: Actions = {
check: async ({ request }) => {
const form = await superValidate(request, zod(usernameSchema));
if (!form.valid) return fail(400, { form });
if(!checkUsername(form.data.username)) {
setError(form, 'username', 'Username is already taken.');
}
return { form };
}
};
```
And finally, an `on:input` event on the input field:
```svelte
<input
name="username"
aria-invalid={$errors.username ? 'true' : undefined}
bind:value={$form.username}
on:input={checkUsername}
/>
{#if $submitting}<img src={spinner} alt="Checking availability" />
{:else if $errors.username}<div class="invalid">{$errors.username}</div>{/if}
```
A full example of a username check is [available on SvelteLab](https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree/username-available-zod).
## Configuration and troubleshooting
Due to the many different use cases, it's hard to set sensible default options for multiple forms. A common issue is that when one form is submitted, the other forms' data are lost. This is due to the page being invalidated by default on a successful response.
If you want to preserve their data, you'd almost certainly want to set `invalidateAll: false` or `applyAction: false` on them, as in the example above. The [use:enhance](/concepts/enhance) option explains the differences between them.
Also check out the [componentization](/components) page for ideas on how to place the forms into separate components, to make `+page.svelte` less cluttered.
## Test it out
Here we have two forms, with separate id's and their `invalidateAll` option set to `false`, to prevent page invalidation, which would otherwise clear the other form when the load function executes again.
<Form {data} />
<Next section={concepts} />
---
File: /src/routes/concepts/nested-data/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Nested data
<Head title="Nested data" />
HTML forms can only handle string values, and the `<form>` element cannot nest other forms, so there is no standardized way to represent a nested data structure or more complex values like dates. Fortunately, Superforms has a solution for this!
## Usage
```ts
const { form, enhance } = superForm(data.form, {
dataType: 'form' | 'json' = 'form'
})
```
### dataType
By simply setting `dataType` to `'json'`, you can store any data structure allowed by [devalue](https://github.com/Rich-Harris/devalue) in the `$form` store, and you don't have to worry about failed coercion, converting strings to objects and arrays, etc.
You don't even have to set a name on the form fields anymore, since the actual data in `$form` is now posted, not the input fields in the HTML. They are now simply UI components for modifying a data model, [as they should be](https://blog.encodeart.dev/rediscovering-mvc). (Name attributes can still be useful for the browser to pre-fill fields though.)
> When `dataType` is set to `'json'`, the `onSubmit` event will contain `FormData`, but it cannot be used to modify the posted data. You need to set or update `$form`, or in special cases, use [jsonData in onSubmit](/concepts/events#jsondata).<br><br>This also means that the `disabled` attribute, which normally prevents input fields from being submitted, will not have that effect. Everything in `$form` will be posted when `dataType` is set to `'json'`.
## Requirements
The requirements for nested data to work are as follows:
1. **JavaScript is enabled in the browser.**
2. **The form has the Superforms [use:enhance](/concepts/enhance) applied.**
## Modifying the form programmatically
To modify the form in code, you can assign `$form.field = ...` directly, but note that this will [taint](/concepts/tainted) the affected fields. If you're using the tainted feature and want to prevent it from happening, instead of an assignment you can use `form.update` with an extra option:
```ts
form.update(
($form) => {
$form.name = "New name";
return $form;
},
{ taint: false }
);
```
The `taint` options are:
```ts
{ taint: boolean | 'untaint' | 'untaint-form' }
```
Which can be used to not only prevent tainting, but also untaint the modified field(s), or the whole form.
## Nested errors and constraints
When your schema contains arrays or objects, you can access them through `$form` as an ordinary object. But how does it work with errors and constraints?
`$errors` and `$constraints` mirror the `$form` data, but with every field or "leaf" in the object replaced with `string[]` and `InputConstraints`, respectively.
### Example
Given the following schema, which describes an array of tag objects:
```ts
const schema = z.object({
tags: z
.object({
id: z.number().int().min(1),
name: z.string().min(2)
})
.array()
});
const tags = [{ id: 1, name: 'test' }];
export const load = async () => {
const form = await superValidate({ tags }, zod(schema));
return { form };
};
```
You can build a HTML form for these tags using an `{#each}` loop:
```svelte
<script lang="ts">
const { form, errors, enhance } = superForm(data.form, {
dataType: 'json'
});
</script>
<form method="POST" use:enhance>
{#each $form.tags as _, i}
<div>
Id
<input
type="number"
data-invalid={$errors.tags?.[i]?.id}
bind:value={$form.tags[i].id} />
Name
<input
data-invalid={$errors.tags?.[i]?.name}
bind:value={$form.tags[i].name} />
{#if $errors.tags?.[i]?.id}
<br />
<span class="invalid">{$errors.tags[i].id}</span>
{/if}
{#if $errors.tags?.[i]?.name}
<br />
<span class="invalid">{$errors.tags[i].name}</span>
{/if}
</div>
{/each}
<button>Submit</button>
</form>
```
> You can't use the loop variable directly, as the value must be bound directly to `$form`, hence the usage of the loop index `i`.
> Take extra care with the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.`, it's easy to miss a question mark, which will lead to confusing errors.
### The result
<Form {data} />
## Arbitrary types in the form
Using the [transport](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) feature of SvelteKit, it's possible to use any type of value in the form and send it back and forth between server and client. To do this, you use the `transport` export from `hooks.ts` and its corresponding option for both `superValidate` and `superForm`. Here's an example with the [decimal.js](https://mikemcl.github.io/decimal.js/) library.
**src/hooks.ts**
```ts
import type { Transport } from '@sveltejs/kit';
import { Decimal } from 'decimal.js';
export const transport: Transport = {
Decimal: {
encode: (value) => value instanceof Decimal && value.toString(),
decode: (str) => new Decimal(str)
}
};
```
When calling `superValidate`:
```ts
import { transport } from '../../hooks.js';
const form = await superValidate(formData, zod(schema), { transport });
```
When calling `superForm`:
```ts
import { transport } from '../../hooks.js';
const { form, errors, enhance } = superForm(data.form, {
dataType: 'json',
transport
});
```
> The transport feature requires at least version 2.11 of SvelteKit! Also note that classes have to be relatively simple to work. Static methods, getters/setters, etc, may cause problems due to how cloning works within Superforms.
## Arrays with primitive values
It's possible to post multiple HTML elements with the same name, so you don't have to use `dataType: 'json'` for arrays of primitive values like numbers and strings. Just add the input fields, **all with the same name as the schema field**, which can only be at the top level of the schema. Superforms will handle the type coercion to array automatically, as long as the fields have the same name attribute:
```ts
export const schema = z.object({
tags: z.string().min(2).array().max(3)
});
```
```svelte
<script lang="ts">
const { form, errors } = superForm(data.form);
</script>
<form method="POST">
<div>Tags</div>
<!-- Display array-level errors (in this case array length) -->
{#if $errors.tags?._errors}
<div class="invalid">{$errors.tags._errors}</div>
{/if}
{#each $form.tags as _, i}
<div>
<input name="tags" bind:value={$form.tags[i]} />
<!-- Display individual tag errors (string length) -->
{#if $errors.tags?.[i]}
<span class="invalid">{$errors.tags[i]}</span>
{/if}
</div>
{/each}
<button>Submit</button>
</form>
```
To summarize, the index `i` of the `#each` loop is used to access `$form.tags`, where the current values are (you cannot use the loop variable), and then the `name` attribute is set to the schema field `tags`, so its array will be populated when posted.
This example, having a `max(3)` limitation of the number of tags, also shows how to display array-level errors with the `$errors.tags._errors` field.
## Validation schemas and nested paths
Validation libraries like Zod can refine the validation, the classic example is to check if two password fields are identical when updating a password. Usually there's a `path` specifier for setting errors on those fields in the refine function:
```ts
const confirmPassword = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"], // path of error
});
```
This works fine for top-level properties, but for nested data you must usually specify that path as an **array**, each segment in its own element, not as a string path as you can do in the `FormPathLeaves` type!
```ts
// OK:
path: ['form', 'tags', 3]
// Will not work with Zod refine and superRefine:
path ['form.tags[3]']
```
<Next section={concepts} />
---
File: /src/routes/concepts/proxy-objects/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Proxy objects
<Head title="Proxy objects" />
Sometimes you get a `string` value from an input field or third-party library, and want it to be automatically converted and updated with a non-string value in your schema. This is what proxies are for.
```ts
import {
// The primitives return a Writable<string>:
booleanProxy,
dateProxy,
intProxy,
numberProxy,
stringProxy,
// The type of the other three depends on the field:
formFieldProxy,
arrayProxy,
fieldProxy
} from 'sveltekit-superforms';
```
The usage for all of them is practically the same. You can use the form store, or the whole superForm as input, which allows you to set a `tainted` option, in case you don't want to taint the form when it updates.
```ts
import { superForm, intProxy } from 'sveltekit-superforms';
// Assume the following schema:
// { id: number }
const superform = superForm(data.form);
const { form, errors, enhance } = superform;
// Returns a Writable<string>
const idProxy = intProxy(form, 'id');
// Use the whole superForm object to prevent tainting
const idProxy2 = intProxy(superform, 'id', { taint: false });
```
Now if you bind to `$idProxy` instead of `$form.id`, the value will be converted to and from an integer, and `$form.id` will be updated automatically.
Note that this kind of conversion will usually happen automatically with `bind:value`. `intProxy` and `numberProxy` are rarely needed, as Svelte [handles this automatically](https://svelte.dev/tutorial/numeric-inputs). But proxies may still be useful if you want to set the value to `undefined` or `null` when the value is falsy, in which case you can use the `empty` option.
> See [the API](/api#proxy-objects) for more details and options for each kind of proxy.
## Nested proxies
You can use a proxy for nested data, like `'user.profile.email'`, in which case parent objects will be created if they don't exist.
## Date input issues
The `date` input type is a bit special. Its underlying data is a string in `yyyy-mm-dd` format, but the `dateProxy` returns an ISO date string as default, so you need to use the `format` option to return the date part only:
```ts
const proxyDate = dateProxy(form, 'date', { format: 'date' });
```
```svelte
<input
name="date"
type="date"
bind:value={$proxyDate}
aria-invalid={$errors.date ? 'true' : undefined}
{...$constraints.date}
min={$constraints.date?.min?.toString().slice(0, 10)}
max={$constraints.date?.max?.toString().slice(0, 10)}
/>
```
We're also using the `min` and `max` constraints to limit the date picker selection. The following example limits the date from today and forward, and also uses the [empty option](/api#dateproxyform-fieldname-options) of the proxy, to set an invalid date to `undefined`. [Code](https://github.com/ciscoheat/superforms-web/blob/main/src/routes/concepts/proxy-objects/Form.svelte)
<Form {data} />
<Next section={concepts} />
---
File: /src/routes/concepts/snapshots/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
import { superForm } from 'sveltekit-superforms/client';
export let data;
const { form, errors, enhance, message, capture, restore, reset } = superForm(data.form, {
taintedMessage: null
});
export const snapshot = { capture, restore }
</script>
# Snapshots
<Head title="Snapshots" />
A nice SvelteKit feature is [snapshots](https://kit.svelte.dev/docs/snapshots), which saves and restores data when the user navigates on the site. This is perfect for saving the form state, and with Superforms, you can take advantage of this in one line of code, as an alternative to a [tainted form message](/concepts/tainted). Note that it only works for browser history navigation though.
## Usage
```ts
const { form, capture, restore } = superForm(data.form);
export const snapshot = { capture, restore };
```
The export has to be on a `+page.svelte` page to work, it cannot be in a component.
> The `options` object contains functions and cannot be serialized for a snapshot. If you modify the options dynamically, make a custom version of the methods to handle the changes.
## Test it out
Modify the form below without submitting, then click the browser back button and then forward again. The form should be restored to its intermediate state.
<Form {form} {errors} {enhance} {message} {reset} />
<Next section={concepts} />
---
File: /src/routes/concepts/spa/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
<Head title="Single-page application (SPA) mode" />
# Single-page applications (SPA)
It's possible to use the whole Superforms library on the client, for example in single page applications or when you want to call an external API instead of the SvelteKit form actions. A SPA is easy to create with SvelteKit, [documented here](https://kit.svelte.dev/docs/single-page-apps).
## Usage
```ts
const { form, enhance } = superForm(data, {
SPA: true
validators: false | ClientValidationAdapter<S>
})
```
By setting the `SPA` option to `true`, the form won't be sent to the server when submitted. Instead, the client-side [validators](/concepts/client-validation#validators) option will determine if the form is valid, and you can then use the [onUpdate](/concepts/events#onupdate) event as a submit handler, for example to call an external API with the form data.
> Remember that the Superforms [use:enhance](/concepts/enhance) must be added to the form for SPA to work.
## Using +page.ts instead of +page.server.ts
Since SPA pages don't have a server representation, you can use [+page.ts](https://kit.svelte.dev/docs/routing#page-page-js) to load initial data. Combined with a route parameter, we can make a CRUD-like page in a straightforward manner:
**src/routes/user/[id]/+page.ts**
```ts
import { error } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
export const _userSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(2),
email: z.string().email()
});
export const load = async ({ params, fetch }) => {
const id = parseInt(params.id);
const request = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`
);
if (request.status >= 400) throw error(request.status);
const userData = await request.json();
const form = await superValidate(userData, zod(_userSchema));
return { form };
};
```
> If no data should be loaded from `+page.ts`, read further down about the `defaults` function.
## Displaying the form
We display the form in `+page.svelte` like before, but with the `SPA` option added, and the `onUpdate` event now being used to validate the form data, instead of on the server:
**src/routes/user/[id]/+page.svelte**
```svelte
<script lang="ts">
import { superForm, setMessage, setError } from 'sveltekit-superforms';
import { _userSchema } from './+page.js';
import { zod } from 'sveltekit-superforms/adapters';
let { data } = $props();
const { form, errors, message, constraints, enhance } = superForm(
data.form,
{
SPA: true,
validators: zod(_userSchema),
onUpdate({ form }) {
// Form validation
if (form.data.email.includes('spam')) {
setError(form, 'email', 'Suspicious email address.');
} else if (form.valid) {
// TODO: Call an external API with form.data, await the result and update form
setMessage(form, 'Valid data!');
}
}
}
);
</script>
<h1>Edit user</h1>
{#if $message}<h3>{$message}</h3>{/if}
<form method="POST" use:enhance>
<label>
Name<br />
<input
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
{...$constraints.name} />
</label>
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
<label>
E-mail<br />
<input
type="email"
aria-invalid={$errors.email ? 'true' : undefined}
bind:value={$form.email}
{...$constraints.email} />
</label>
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
<button>Submit</button>
</form>
```
The validation in `onUpdate` is almost the same as validating in a form action on the server. Nothing needs to be returned at the end since all modifications to the `form` parameter in `onUpdate` will update the form.
> Of course, since this validation is done on the client, it's easy to tamper with. See it as a convenient way of collecting the form data before doing a proper server validation through an external API.
## Without a +page.ts file
Since you can't use top-level await in Svelte components, you can't use `superValidate` directly in `+page.svelte`, as it is async. But if you want the default values only for the schema, you can import `defaults` to avoid having a `+page.ts`.
```svelte
<script lang="ts">
import { superForm, defaults } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { loginSchema } from '$lib/schemas';
const { form, errors, enhance } = superForm(defaults(zod(loginSchema)), {
SPA: true,
validators: zod(loginSchema),
onUpdate({ form }) {
if (form.valid) {
// TODO: Call an external API with form.data, await the result and update form
}
}
});
</script>
```
### With initial top-level data
If you have initial data in the top level of the component, you can pass it as a first parameter to `defaults`, **but remember that it won't be validated**. There's a trick though; if you want to show errors for the initial data, you can call `validateForm` directly after `superForm`. The `validators` option must be set for this to work:
```ts
const initialData = { name: 'New user' };
const { form, errors, enhance, validateForm } = superForm(
defaults(initialData, zod(loginSchema)), {
SPA: true,
validators: zod(loginSchema)
// ...
}
);
validateForm({ update: true });
```
<Next section={concepts} />
---
File: /src/routes/concepts/strict-mode/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Next from '$lib/Next.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Strict mode
<Head title="Strict mode" />
Superforms is quite forgiving with missing fields in the posted data. The assumption is that if you add a field to a schema, it will sooner or later be added to the form, so the [default value](/default-values) is applied to all missing schema fields.
But you may want to be more strict and assert that the input data must exist, otherwise something is wrong. For that, you can use strict mode when calling `superValidate`:
```ts
const form = await superValidate(request, zod(schema), { strict: true });
```
Now the following will be true:
- Any missing **required** field will be reported as an error.
- Errors will be displayed as default (can be changed with the `errors` option).
> As checkboxes aren't posted unless checked, they will fail validation in strict mode unless you add an empty hidden field for the checkbox, signifying `false`.
## Catch-all schema
Some validation libraries have a "catch-all" feature, allowing extra fields to be posted and validated. Here's an example of how to use it with Zod:
```ts
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1)
})
.catchall(z.number().int()); // All unknown fields should be integers
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(schema));
if (!form.valid) {
return fail(400, { form });
}
// Typed as string, as expected
console.log(form.data.name);
// All other keys are typed as number
console.log(form.data.first, form.data.second);
return { form };
}
};
```
<Next section={concepts} />
---
File: /src/routes/concepts/submit-behavior/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Next from '$lib/Next.svelte'
import { concepts } from '$lib/navigation/sections'
</script>
# Submit behavior
<Head title="Submit behavior" />
When a form is submitted, it's important for the UX to show that things are happening on the server. Superforms provides you with [loading timers](/concepts/timers) and the following options for handling this:
## Usage
```ts
const { form, enhance } = superForm(data.form, {
clearOnSubmit: 'message' | 'errors' | 'errors-and-message' | 'none' = 'message'
multipleSubmits: 'prevent' | 'allow' | 'abort' = 'prevent'
})
```
### clearOnSubmit
The `clearOnSubmit` option decides what should happen to the form when submitting. It can clear the [status message](/concepts/messages), all the [errors](/concepts/error-handling), both, or none. The default is to clear the message.
If you don't want any jumping content, which could occur when errors and messages are removed from the DOM, setting it to `none` can be useful.
### multipleSubmits
This one handles the occurence of multiple form submissions, before a result has been returned.
- When set to the default `prevent`, the form cannot be submitted again until a result is returned, or the `timeout` state is reached (see the section about [loading timers](/concepts/timers)).
- `abort` is the next sensible approach, which will cancel the previous request before submitting again.
- Finally, `allow` will pass through any number of frenetic clicks on the submit button!
<Next section={concepts} />
---
File: /src/routes/concepts/tainted/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Tainted fields
<Head title="Tainted form fields" />
When the form data is modified, through modifying `$form`, the modified data (and in turn the form), is considered _tainted_, also known as "dirty" in other form libraries.
A Superforms feature is to prevent the user from losing data when navigating away from a tainted form.
## Usage
```ts
const { form, enhance } = superForm(data.form, {
taintedMessage: string | (() => Promise<boolean>) | boolean = false
})
```
You can set the option to `true` to have a default message (in english) shown when navigating away from a tainted form, or set your own message with a `string` value. Note that this message will only be displayed when navigating to a page within the same site. When closing the tab or reloading the page, a browser default message will be shown instead.
Alternatively, you can set `taintedMessage` to a `() => Promise<boolean>` function that should resolve to `true` if navigating away is ok. This enables you to provide your own dialog:
```ts
const { form, enhance } = superForm(data.form, {
taintedMessage: () => {
return new Promise((resolve) => {
modalStore.trigger({
type: 'confirm',
title: 'Do you want to leave?',
body: 'Changes you made may not be saved.',
response: resolve
});
});
}
});
```
The code demonstrates the custom dialog with a [Skeleton modal](https://www.skeleton.dev/utilities/modals). Try it out below! Modify the form, then click the back button. A modal should pop up, preventing you from losing the changes:
<Form {data} />
## Untainting the form
When a successful validation result is returned for the form, with a `valid` status set to `true`, the form is automatically marked as untainted.
Try that by posting the form with valid values. The tainted message should not appear when browsing away from the page.
## Check if the form is tainted
An `isTainted` method is available on `superForm`, to check whether any part of the form is tainted:
```svelte
<script lang="ts">
const { form, enhance, tainted, isTainted } = superForm(form.data);
// Check the whole form
if(isTainted()) {
console.log('The form is tainted')
}
// Check a part of the form
if(isTainted('name')) {
console.log('The name field is tainted')
}
</script>
<!-- Make the function reactive by passing the $tainted store -->
<button disabled={!isTainted($tainted)}>Submit</button>
<!-- It even works with individual fields -->
<button disabled={!isTainted($tainted.name)}>Submit name</button>
```
## Preventing tainting the form
If you want to modify the `form` store without tainting any fields, you can update it with an extra option:
```ts
const { form } = superForm(data.form);
form.update(
($form) => {
$form.name = "New name";
return $form;
},
{ taint: false }
);
```
The `taint` options are:
```ts
{ taint: boolean | 'untaint' | 'untaint-form' }
```
Which can be used to not only prevent tainting, but also untaint the modified field(s), or untainting the whole form.
> For login and registration forms, password managers could automatically taint the form when inserting saved usernames and passwords.
<Next section={concepts} />
---
File: /src/routes/concepts/timers/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Form from './Form.svelte'
import Next from '$lib/Next.svelte'
import Timers from '$lib/Timers.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { concepts } from '$lib/navigation/sections'
export let data;
</script>
# Loading timers
<Head title="Timers for loading spinners" />
It's important that users understand that things are happening when they submit a form. Loading timers can be used to provide feedback when there is a server response delay, for example by displaying a loading spinner icon.
## Usage
```ts
const { form, enhance, submitting, delayed, timeout } = superForm(data.form, {
delayMs?: 500
timeoutMs?: 8000
})
```
`delayMs` should be positive and always smaller than or equal to `timeoutMs`, otherwise the timer behavior will be undefined. And of course, the Superforms [use:enhance](/concepts/enhance) must be added to the form element, since this is client-side behavior.
## Submit state
After a certain time when the form is submitted, determined by `delayMs` and `timeoutMs`, the timers changes state. The states are:
<Timers />
These states affect the readable stores `submitting`, `delayed` and `timeout`, returned from `superForm`. They are not mutually exclusive, so `submitting` won't change to `false` when `delayed` becomes `true`.
## Loading indicators
A perfect use for these timers is to show a loading indicator while the form is submitting:
**src/routes/+page.svelte**
```svelte
<script lang="ts">
const { form, errors, enhance, delayed } = superForm(data.form);
import spinner from '$lib/assets/spinner.svg';
</script>
<form method="POST" use:enhance>
<button>Submit</button>
{#if $delayed}<img src={spinner} />{/if}
</form>
```
The reason for using `delayed` instead of `submitting` is based on the article [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/), which states that for short waiting periods, no feedback is required except to display the result. Therefore, `delayed` is used to show a loading indicator after a little while, not instantly.
## Visualizing the timers
Submit the following form and play around with the different settings. Different loading spinners are set to display when `delayed` and `timeout` are true respectively.
The default [multipleSubmits](/concepts/submit-behavior#multiplesubmits) setting prevents the form from being submitted multiple times, until the `timeout` state is reached. Click multiple times to see the effect of that.
<Form data={data} />
By experimenting with the timers and the delay between them, it's certainly possible to prevent the feeling of unresponsiveness.
<Next section={concepts} />
---
File: /src/routes/contributing/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Donating from './Donating.svelte'
import Message from '$lib/sponsoring/Message.svelte'
</script>
<Message />
<Head title="Contributing, donating and sponsoring" />
# Contributing
Contributions to Superforms are very welcome! The issues and discussion pages on [Github](https://github.com/ciscoheat/sveltekit-superforms) are always open for comments, and if you want to contribute some code, it's quite easy. Just fork either the [Superforms](https://github.com/ciscoheat/sveltekit-superforms) repository or its [website](https://github.com/ciscoheat/superforms-web), and then you just need to execute the following to be up and running:
```
npm install
npm run dev
```
```
pnpm install
pnpm dev
```
If you find a typo on the website, you can make a quick PR directly in its [Github repository](https://github.com/ciscoheat/superforms-web/tree/main/src/routes).
## Donations
If you want to contribute to the future development of Superforms, you can make a donation:
<Donating />
If you have appreciated my support on Discord and Github, or want to ensure that things keep rolling, please support open source software in this way.
---
File: /src/routes/crud/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Youtube from '$lib/Youtube.svelte'
</script>
# Designing a CRUD interface
<Head title="CRUD tutorial for Superforms" />
An excellent use case for Superforms is a backend interface, commonly used as in the acronym **CRUD** (Create, Read, Update, Delete):
1. Display an empty form
1. POST the form, validate the data
1. Create a new entity with the data **(Create)**
1. Fetch the entity **(Read)**
1. Display it in a form
1. POST the form, validate the data
1. Update the entity with the data **(Update)**
1. Delete the entity **(Delete)**
1. ???
1. Profit!
Because you can send the data model to the `superValidate` function and have the form directly populated, it becomes quite easy to implement the above steps.
## Getting started
To follow along, there are three ways:
### Video tutorial
Instead of the text version below, here's the video version. It's using version 1 of Superforms, but the differences are small; use an adapter and add `resetForm: false` to the options.
<Youtube id="nN72awrXsHE" />
### SvelteLab
The code is available [on SvelteLab](https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree/crud-zod). Just click the link, and you're up and running in the browser in less than 30 seconds!
### New SvelteKit project
Start from scratch in a new SvelteKit project by executing one of the following commands in your project directory:
```
npx sv create sf-crud
```
```
pnpx sv create sf-crud
```
Select **Skeleton project** and **Typescript syntax** at the prompts, the rest is up to you. Then add this to `<head>` in **src/app.html** for a much nicer visual experience:
```html
<link rel="stylesheet" href="https://unpkg.com/[email protected]/normalize.css" />
<link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura.css" />
```
## Start - Creating a test database
When you have the code up and running, we need a user schema, and a mock database for testing. We will use Zod as a validation library, but its schema can easily be converted to others.
**src/lib/users.ts**
```ts
import { z } from 'zod';
// See https://zod.dev/?id=primitives for schema syntax
export const userSchema = z.object({
id: z.string().regex(/^\\d+$/),
name: z.string().min(2),
email: z.string().email()
});
type UserDB = z.infer<typeof userSchema>[];
// Let's worry about id collisions later
export const userId = () => String(Math.random()).slice(2);
// A simple user "database"
export const users: UserDB = [
{
id: userId(),
name: 'Important Customer',
email: '[email protected]'
},
{
id: userId(),
name: 'Super Customer',
email: '[email protected]'
}
];
```
## Form vs. database schemas
When starting on the server page, we'll encounter a thing about validation schemas. Our `userSchema` is for **database integrity**, so an `id` **must** exist there. But we want to create an entity, and must therefore allow `id` not to exist when creating users.
Fortunately, Zod makes it quite easy to append a modifier to a field without duplicating the whole schema by extending it:
**src/routes/users/[[id]]/+page.server.ts**
```ts
import { userSchema } from '$lib/users';
const crudSchema = userSchema.extend({
id: userSchema.shape.id.optional()
});
```
(Of course, the original `userSchema` is kept intact.)
With this, **Create** and **Update** can now use the same schema, which means that they can share the same user interface. This is a fundamental idea in Superforms: you can pass either empty data or an entity partially matching the schema to `superValidate`, and it will generate default values for any non-specified fields, ensuring type safety.
## Reading a user from the database
Let's add a load function to the page, using the SvelteKit route parameters to look up the requested user:
**src/routes/users/[[id]]/+page.server.ts**
```ts
import { superValidate, message } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { error, fail, redirect } from '@sveltejs/kit';
import { users, userId } from '$lib/users';
export const load = async ({ url, params }) => {
// READ user
const user = users.find((u) => u.id == params.id);
if (params.id && !user) throw error(404, 'User not found.');
// If user is null, default values for the schema will be returned.
const form = await superValidate(user, zod(crudSchema));
return { form, users };
};
```
Some simple logic is used to find the user, and then detect if a 404 should be displayed. At the end, we're returning `form` as usual, but also `users`, so they can be displayed as a list.
(Sometimes, CRUDL is used as an acronym, since listing is also fundamental to data management.)
Now that we have loaded the data, let's display it:
**src/routes/users/[[id]]/+page.svelte**
```svelte
<script lang="ts">
import { page } from '$app/state';
import { superForm } from 'sveltekit-superforms';
let { data } = $props();
const { form, errors, constraints, enhance, delayed, message } = superForm(
data.form, {
resetForm: false
}
);
</script>
{#if $message}
<h3 class:invalid={page.status >= 400}>{$message}</h3>
{/if}
<h2>{!$form.id ? 'Create' : 'Update'} user</h2>
```
There are plenty of variables extracted from `superForm`; refer to the [API reference](/api#superform-return-type) for a complete list. We've also setting the `resetForm` to `false`, since the data should be kept after editing.
We've also prepared a status message, using `page.status` to test for success or failure, and we're using `$form.id` to display a "Create user" or "Update user" title. Now let's add the form itself:
**src/routes/users/[[id]]/+page.svelte** (continued)
```svelte
<form method="POST" use:enhance>
<input type="hidden" name="id" bind:value={$form.id} />
<label>
Name<br />
<input
name="name"
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
{...$constraints.name} />
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
</label>
<label>
E-mail<br />
<input
name="email"
type="email"
aria-invalid={$errors.email ? 'true' : undefined}
bind:value={$form.email}
{...$constraints.email} />
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
</label>
<button>Submit</button> {#if $delayed}Working...{/if}
</form>
<style>
.invalid {
color: red;
}
</style>
```
Remember to use [SuperDebug](/super-debug) if you want to see your form data live!
```svelte
<script>
import SuperDebug from 'sveltekit-superforms';
</script>
<SuperDebug data={$form} />
```
## Creating and Updating a user
With this, the form should be ready for creating a user. Let's add the form action for that:
**src/routes/users/[[id]]/+page.server.ts**
```ts
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(crudSchema));
if (!form.valid) return fail(400, { form });
if (!form.data.id) {
// CREATE user
const user = { ...form.data, id: userId() };
users.push(user);
return message(form, 'User created!');
} else {
// UPDATE user
const index = users.findIndex((u) => u.id == form.data.id);
if (index == -1) throw error(404, 'User not found.');
users[index] = { ...form.data, id: form.data.id };
return message(form, 'User updated!');
}
return { form };
}
};
```
This is where you should access your database API. In our case, we're only doing some array manipulations.
With this, we have 3 out of 4 letters of CRUD in about 150 lines of code, half of it HTML!
## Deleting a user
To delete a user, we can make use of the HTML `button` element, which can have a name and a value that will be passed only if that specific button was used to post the form. Add this at the end of the form:
**src/routes/users/[[id]]/+page.svelte**
```svelte
{#if $form.id}
<button
name="delete"
on:click={(e) => !confirm('Are you sure?') && e.preventDefault()}
class="danger">Delete user</button>
{/if}
```
In the form action, we now use the `FormData` from the request to check if the delete button was pressed.
We can also add a delayed button in the form to mimick the behavior of a slow network call:
```svelte
<button name="delay" class="delay">Submit delayed</button>
```
We shouldn't use the schema since `delete` and `delayed` are not a part of the user, but it's not a big change:
**src/routes/users/[[id]]/+page.server.ts**
```ts
export const actions: Actions = {
default: async ({ request }) => {
const formData = await request.formData();
const form = await superValidate(formData, zod(crudSchema));
if (formData.has('delay')) {
await new Promise((r) => setTimeout(r, 2000));
}
if (!form.valid) return fail(400, { form });
if (!form.data.id) {
// CREATE user
const user = { ...form.data, id: userId() };
users.push(user);
return message(form, 'User created!');
} else {
const index = users.findIndex((u) => u.id == form.data.id);
if (index == -1) throw error(404, 'User not found.');
if (formData.has('delete')) {
// DELETE user
users.splice(index, 1);
throw redirect(303, '/users');
} else {
// UPDATE user
users[index] = { ...form.data, id: form.data.id };
return message(form, 'User updated!');
}
}
}
};
```
Now all four CRUD operations are complete! An issue, however, is that we have to redirect after deleting to avoid a 404, so we cannot use `form.message` to show "User deleted", since the validation data won't exist after redirecting.
Redirecting with a message is a general problem; for example, maybe we'd like to redirect to the newly created user after it's been created. Fortunately, there is a solution; the sister library to Superforms handles this. [Read more about it here](/flash-messages).
## Listing the users
The last loose thread is to display a list of the users. It'll be quite trivial; add this to the top of `+page.svelte`:
**src/routes/+page.svelte**
```svelte
<h3>Users</h3>
<div class="users">
{#each data.users as user}
<a href="/users/{user.id}">{user.name}</a>
{/each}
</div>
```
And some styling for everything at the end:
```svelte
<style>
.invalid {
color: red;
}
.danger {
background-color: brown;
}
.delay {
background-color: lightblue;
}
form {
display: flex;
flex-direction: column;
gap: 1em;
align-items: flex-start;
margin-bottom: 2em;
}
hr {
width: 100%;
margin-block: 2em;
}
.users {
columns: 3 150px;
}
.users > * {
display: block;
white-space: nowrap;
overflow-x: hidden;
}
</style>
```
That's it! Thank you for following along, the code is [available here](https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree/crud-zod) on SvelteLab.
If you have any questions, see the [help & support](/support) page.
---
File: /src/routes/default-values/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# Default values
<Head title="Default values" />
When `superValidate` encounters a schema field that isn't optional, and when its supplied data is empty, a default value is returned to the form, to ensure that the type is correct:
| type | value |
| ------- | ----------- |
| string | `""` |
| number | `0` |
| boolean | `false` |
| Array | `[]` |
| object | `{}` |
| bigint | `BigInt(0)` |
| symbol | `Symbol()` |
## Changing a default value
You can, of course, set your own default values in the schema, using the `default` method in Zod, for example. You can even abuse the typing system a bit to handle the classic "agree to terms" checkbox:
```ts
const schema = z.object({
age: z.number().positive().default('' as unknown as number),
agree: z.literal(true).default(false as true)
});
```
This looks a bit strange, but will ensure that the age isn't set to 0 as default (which will hide placeholder text in the input field), and also ensure that the agree checkbox is unchecked as default while also only accepting `true` (checked) as a value.
> Since the default value will not correspond to the type, this will not work when using Zod's [refine](https://zod.dev/?id=refine) on the whole schema. Validation itself is no problem though, `form.valid` will be `false` if these values are posted, which should be the main determinant of whether the data is trustworthy.
## optional vs. nullable
Fields set to `null` will take precedence over `undefined`, so a field that is both `nullable` and `optional` will have `null` as its default value. Otherwise, it's `undefined`.
## Explicit defaults
Multi-type unions must have an explicit default value, or exactly one of the union types must have one. Enums doesn't, otherwise it wouldn't be possible to have a required enum field. If no explicit default, the first enum value will be used.
```ts
const schema = z.object({
either: z.union([z.number(), z.string()]).default(123),
fish: z.enum(['Salmon', 'Tuna', 'Trout']) // Default will be 'Salmon'
});
```
If the field is nullable or optional, there's no need for a default value (but it can still be set).
```ts
const schema2 = z.object({
fish: z.enum(['Salmon', 'Tuna', 'Trout']).nullable()
});
```
## Enums and group inputs
If you're using the enum for radio buttons or a select menu, you may not want any option to be initally selected. This can be achieved in a similar way to the misuse of the default value above, by setting it to an empty string and casting it:
```ts
const schema2 = z.object({
fish: z.enum(['Salmon', 'Tuna', 'Trout']).default('' as 'Salmon')
});
```
---
File: /src/routes/examples/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Libraries from '$lib/LibrariesButtons.svelte'
import ExampleList from './ExampleList.svelte'
</script>
# Playground
<Head title="Superform examples and playground" />
For testing Superforms, bug reporting, support questions, or as a start for your project, click on your chosen library to open a SvelteKit project with a basic form in SvelteLab:
<Libraries url="https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree" name="adapter" target="_blank" />
# Examples
<ExampleList />
---
File: /src/routes/faq/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# FAQ
<Head title="FAQ" />
### I see the data in $form, but it's not posted to the server?
The most common mistake is to forget the `name` attribute on the input field. If you're not using `dataType: 'json'` (see [nested data](/concepts/nested-data)), the form is treated as a normal HTML form, which requires a name attribute for posting the form data.
---
### How can I prevent the form from being reset after it's submitted?
Use the `resetForm: false` option for `superForm`, as described on the [use:enhance](/concepts/enhance#resetform) page.
---
### Why do I need to call superValidate in the load function?
The object returned from `superValidate`, called [SuperValidated](/api#supervalidate-return-type), is used to instantiate a `superForm`, just like a required argument in a constructor. It contains [constraints](/concepts/client-validation#built-in-browser-validation), [form id](/concepts/multiple-forms) based on the schema, an internal structure for handling errors in [nested data](/concepts/nested-data), potential [initial errors](/concepts/error-handling#initial-form-errors), and more. Therefore you need to call `superValidate` in the load function, so its data can be sent to the client and used when calling `superForm` there.
In special cases you can send an object with just the form data to `superForm`, but that is only for:
- Simple forms with no nested data
- No constraints used
- No initial errors
- Only one form on the page (the [form id](/concepts/multiple-forms) has to be set manually otherwise).
Another reason is that when using a server load function (`+page.server.ts`) to populate the form, the page will work with [SSR](https://kit.svelte.dev/docs/page-options).
---
### How to handle file uploads?
From version 2, file uploads are handled by Superforms. Read all about it on the [file uploads](/concepts/files) page.
---
### Can I use endpoints instead of form actions?
Yes, there is a helper function for constructing an `ActionResult` that can be returned from SvelteKit [endpoints](https://kit.svelte.dev/docs/routing#server). See [the API reference](/api#actionresulttype-data-options--status) for more information.
---
### Can I post to an external API?
If the API doesn't return an [ActionResult](https://kit.svelte.dev/docs/types#public-types-actionresult) with the form data, you cannot post to it directly. Instead you can use the [SPA mode](/concepts/spa) of Superforms and call the API with [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) or similar in the `onUpdate` event.
---
### How to submit the form programmatically?
Use the `submit` method on the `superForm` object.
---
### Can a form be factored out into a separate component?
Yes - the answer has its own [article page here](/components).
---
### How can I return additional data together with the form?
You're not limited to just `return { form }` in load functions and form actions; you can return anything else together with the form variables (which can also be called anything you'd like).
#### From a load function
```ts
export const load = (async ({ locals }) => {
const loginForm = await superValidate(zod(loginSchema));
const userName = locals.currentUser.name;
return { loginForm, userName };
})
```
It can then be accessed in `PageData` in `+page.svelte`:
```svelte
<script lang="ts">
let { data } = $props();
const { form, errors, enhance } = superForm(data.loginForm);
</script>
<p>Currently logged in as {data.userName}</p>
```
#### From a form action
Returning extra data from a form action is convenient with a [status message](/concepts/messages) if the data is simple, but for larger data structures you can also return it directly, in which case it can be accessed in the [onUpdate](/concepts/events#onupdate) event, or in `ActionData`:
```ts
export const actions = {
default: async ({ request, locals }) => {
const form = await superValidate(request, zod(schema));
if (!form.valid) return fail(400, { form });
// Return the username as extra data:
const userName = locals.currentUser.name;
return { form, userName };
}
};
```
**Using onUpdate**
```svelte
<script lang="ts">
import { superForm, type FormResult } from 'sveltekit-superforms';
import type { ActionData } from './$types.js';
export let data;
const { form, errors, message, enhance } = superForm(data.form, {
onUpdate({ form, result }) {
const action = result.data as FormResult<ActionData>;
if (form.valid && action.userName) {
// Do something with the extra data
}
}
});
</script>
```
**Using ActionData**
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client'
let { data, form } = $props();
// Need to rename form here, since it's used by ActionData.
const { form: formData, errors, enhance } = superForm(data.form);
</script>
{#if form?.userName}
<p>Currently logged in as {form.userName}</p>
{/if}
```
---
### What about the other way around, posting additional data to the server?
You can add additional input fields to the form that aren't part of the schema, including files (see the next question), to send extra data to the server. They can then be accessed with `request.formData()` in the form action:
```ts
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const form = await superValidate(formData, zod(schema));
if (!form.valid) return fail(400, { form });
if (formData.has('extra')) {
// Do something with the extra data
}
return { form };
}
};
```
You can also add form data programmatically in the [onSubmit](/concepts/events#onsubmit) event:
```ts
const { form, errors, enhance } = superForm(data.loginForm, {
onSubmit({ formData }) {
formData.set('extra', 'value')
}
})
```
The onSubmit event is also a good place to modify `$form`, in case you're using [nested data](/concepts/nested-data) with `dataType: 'json'`.
---
### I'm getting JSON.parse errors as response when submitting a form, why?
This is related to the previous question. You must always return an `ActionResult` as a response to a form submission, either through a form action, where it's done automatically, or by constructing one with the [actionResult](/api#actionresulttype-data-options--status) helper.
If for some reason a html page or plain text is returned, for example when a proxy server fails to handle the request and returns its own error page, the parsing of the result will fail with the slightly cryptic JSON error message.
---
### Why am I'm getting TypeError: The body has already been consumed?
This happens if you access the form data of the request before calling `superValidate`, for example when debugging or calling it multiple times during the same request.
To fix the problem, use the form data as an argument instead of `request` or `event`:
```ts
export const actions = {
default: async ({ request }) => {
// Get the form data, which will empty the request stream
const formData = await request.formData();
console.log(formData); // Debugging the raw form data
// Cannot use event or request at this point
const form = await superValidate(formData, zod(schema));
}
};
```
---
### Why does the form get tainted without any changes, when I use a select element?
If the schema field for the select menu doesn't have an empty string as default value, for example when it's optional, and you have an empty first option, like a "Please select item" text, the field will be set to that empty string, tainting the form.
It can be fixed by setting the option and the default schema value to an empty string, even if it's not its proper type. See [this section](/default-values#changing-a-default-value) for an example.
---
### How to customize error messages directly in the validation schema?
You can add them as parameters to most schema methods. [Here's an example](/concepts/error-handling#customizing-error-messages-in-the-schema).
---
### Can you use Superforms without any data, for example with a delete button on each row in a table?
That's possible with an empty schema, or using the `$formId` store with the button to set the form id dynamically. See [this SvelteLab project](https://sveltelab.dev/github.com/ciscoheat/superforms-examples/tree/list-actions-zod) for an example.
---
### I want to reuse common options, how to do that easily?
When you start to configure the library to suit your stack, you can create an object with default options that you will refer to instead of `superForm`:
```ts
import { superForm } from 'sveltekit-superforms';
export type Message = {
status: 'success' | 'error' | 'warning';
text: string;
};
// If no strongly type message is needed, leave out the M type parameter
export function mySuperForm<
T extends Record<string, unknown>,
M extends Message = Message,
In extends Record<string, unknown> = T
>(...params: Parameters<typeof superForm<T, M, In>>) {
return superForm<T, M, In>(params[0], {
// Your defaults here
errorSelector: '.has-error',
delayMs: 300,
...params[1]
});
}
```
---
File: /src/routes/flash-messages/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# Flash messages
<Head title="Integrate Superforms with sveltekit-flash-message" />
[Status messages](/concepts/messages) are useful, but redirects will cause them to be lost, because they need to be returned in `{ form }`, usually as a response from a POST request.
Since it's common to redirect after a successful post, especially in backend interfaces, the `form.message` property isn't a general solution for displaying status messages.
The sister library to Superforms is called [sveltekit-flash-message](https://github.com/ciscoheat/sveltekit-flash-message), a useful addon that handles temporary messages sent with redirects.
## Usage
The library works together with Superforms **without any extra configuration**, usually you can replace the Superforms [status messages](/concepts/messages) with the flash message, and that will work very well.
### Example
After a successful post, it's standard practice to [Redirect After Post](https://www.theserverside.com/news/1365146/Redirect-After-Post). Not so much today with progressive enhancement, but for non-JS users it's still important to redirect with a GET request, to avoid double-posting. (And of course, if you need to redirect to another route, it's unavoidable.)
So at the end of the form action, use the `redirect` function from `sveltekit-flash-message`. But you may also want to stay on the same page, displaying a toast message if something went wrong with the form submission. This is easily done with the `setFlash` function:
```ts
import { redirect, setFlash } from 'sveltekit-flash-message/server';
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request, cookies }) => {
const form = await superValidate(request, your_adapter(schema));
if (!form.valid) {
// Stay on the same page and set a flash message
setFlash({ type: 'error', message: "Please check your form data." }, cookies);
return fail(400, { form });
}
// TODO: Do something with the validated form.data
// All ok, redirect with a flash message on another page
redirect('/posts', { type: 'success', message: "Post created!" }, cookies);
}
};
```
---
File: /src/routes/formsnap/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import formsnap from './formsnap.svg?raw'
</script>
<h1 class="logo">
{@html formsnap}
</h1>
<Head title="Integrate Superforms with Formsnap" />
As you may have seen on the [componentization](/components) page, quite a bit of boilerplate can add up for a form, and then we haven't even touched on the subject of accessibility.
Fortunately, the UI-component guru Hunter Johnston, aka [@huntabyte](https://twitter.com/huntabyte), has done the community a great service with his library [Formsnap](https://www.formsnap.dev/)! It not only simplifies how to put your forms into components, but also adds top-class accessibility with no effort.
This is the style you can expect when using Formsnap, compared to manually putting attributes on individual form fields:
```svelte
<form method="POST" use:enhance>
<Field {form} name="name">
<Control let:attrs>
<Label>Name</Label>
<input {...attrs} bind:value={$formData.name} />
</Control>
<Description>Be sure to use your real name.</Description>
<FieldErrors />
</Field>
<Field {form} name="email">
<Control let:attrs>
<Label>Email</Label>
<input {...attrs} type="email" bind:value={$formData.email} />
</Control>
<Description>It's preferred that you use your company email.</Description>
<FieldErrors />
</Field>
<Field {form} name="password">
<Control let:attrs>
<Label>Password</Label>
<input {...attrs} type="password" bind:value={$formData.password} />
</Control>
<Description>Ensure the password is at least 10 characters.</Description>
<FieldErrors />
</Field>
</form>
```
If it suits you, please check out the [Formsnap](https://www.formsnap.dev/) library, it is really nice! 💥
<style>
.logo {
width: 240px;
}
</style>
---
File: /src/routes/get-started/[...lib]/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte';
import Form from './Form.svelte';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import Installer from './Installer.svelte';
import SvelteLab from './SvelteLab.svelte';
import { getSettings } from '$lib/settings';
export let data;
const settings = getSettings();
if(data.lib) {
$settings.lib = data.lib;
}
let formComponent
$: form = formComponent && formComponent.formData()
</script>
<Head title="Get started - Tutorial for Superforms" />
# Get started
<Installer />
Select your environment above and run the install command in your project folder.
{#if $settings.lib == 'json-schema'}
If you're using JSON Schema on the client, you also need to modify **vite.config.ts**:
```ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
optimizeDeps: {
include: ['@exodus/schemasafe'] // Add this to make client-side validation work
}
});
```
{/if}
If you're starting from scratch, create a new SvelteKit project:
{#if $settings.pm == 'npm i -D'}
```npm
npx sv create my-app
```
{:else if $settings.pm == 'pnpm i -D'}
```npm
pnpx sv create my-app
```
{:else if $settings.pm == 'yarn add --dev'}
```npm
npx sv create my-app
```
{/if}
<SvelteLab lib={$settings.lib} />
{#if ['', 'ajv', 'n/a'].includes($settings.lib)}
> Please select a validation library above before continuing, as the tutorial changes depending on that.
{/if}
## Creating a Superform
This tutorial will create a Superform with a name and email address, ready to be expanded with more form data.
### Creating a validation schema
The main thing required to create a Superform is a validation schema, representing the form data for a single form:
{#if $settings.lib == 'arktype'}
```ts
import { type } from 'arktype';
const schema = type({
name: 'string',
email: 'email'
});
```
{:else if $settings.lib == 'class-validator'}
```ts
import { IsEmail, IsString, MinLength } from 'class-validator';
class ClassValidatorSchema {
@IsString()
@MinLength(2)
name: string = '';
@IsString()
@IsEmail()
email: string = '';
}
export const schema = ClassValidatorSchema;
```
{:else if $settings.lib == 'effect'}
```ts
import { Schema } from 'effect';
// effect deliberately does not provide email parsing out of the box
// https://github.com/Effect-TS/schema/issues/294
// here is a simple email regex that does the job
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/;
const schema = Schema.Struct({
name: Schema.String.annotations({ default: 'Hello world!' }),
email: Schema.String.pipe(
Schema.filter((s) => emailRegex.test(s) || 'must be a valid email', {
jsonSchema: { format: 'email' }
})
)
});
```
{:else if $settings.lib == 'joi'}
```ts
import Joi from 'joi';
const schema = Joi.object({
name: Joi.string().default('Hello world!'),
email: Joi.string().email().required()
});
```
{:else if $settings.lib == 'json-schema'}
```ts
import type { JSONSchema } from 'sveltekit-superforms';
export const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 2, default: 'Hello world!' },
email: { type: 'string', format: 'email' }
},
required: ['name', 'email'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#'
} as const satisfies JSONSchema; // Define as const to get type inference
```
> Currently, definitions and references ($ref properties in the JSON Schema) aren't supported. You need to resolve the references yourself before using the adapter.
{:else if $settings.lib == 'superstruct'}
```ts
import { object, string, defaulted, define } from 'superstruct';
const email = () => define<string>('email', (value) => String(value).includes('@'));
export const schema = object({
name: defaulted(string(), 'Hello world!'),
email: email()
});
```
{:else if $settings.lib == '@sinclair/typebox'}
```ts
import { Type } from '@sinclair/typebox';
const schema = Type.Object({
name: Type.String({ default: 'Hello world!' }),
email: Type.String({ format: 'email' })
});
```
{:else if $settings.lib == 'valibot'}
```ts
import { object, string, email, optional, pipe, minLength } from 'valibot';
export const schema = object({
name: pipe(optional(string(), 'Hello world!'), minLength(2)),
email: pipe(string(), email())
});
```
{:else if $settings.lib == '@vinejs/vine'}
```ts
import Vine from '@vinejs/vine';
const schema = Vine.object({
name: Vine.string(),
email: Vine.string().email()
});
```
{:else if $settings.lib == 'yup'}
```ts
import { object, string } from 'yup';
const schema = object({
name: string().default('Hello world!'),
email: string().email().required()
});
```
{:else if $settings.lib == 'zod'}
```ts
import { z } from 'zod';
const schema = z.object({
name: z.string().default('Hello world!'),
email: z.string().email()
});
```
{:else}
> Select a validation library at the top of the page to see the example code.
{/if}
#### Schema caching
Define the schema *outside* the load function, on the top level of the module. **This is very important to make caching work.** The adapter is memoized (cached) with its arguments, so they must be kept in memory.
Therefore, define the schema, its options and potential defaults on the top level of a module, so they always refer to the same object.
### Initializing the form in the load function
To initialize the form, you import `superValidate` and an adapter for your validation library of choice in a load function:
**src/routes/+page.server.ts**
{#if $settings.lib == 'arktype'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { arktype } from 'sveltekit-superforms/adapters';
import { type } from 'arktype';
// Define outside the load function so the adapter can be cached
const schema = type({
name: 'string',
email: 'email'
});
// Defaults should also be defined outside the load function
const defaults = { name: 'Hello world!', email: '' };
export const load = async () => {
// Arktype requires explicit default values for now
const form = await superValidate(arktype(schema, { defaults }));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'class-validator'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { classvalidator } from 'sveltekit-superforms/adapters';
import { IsEmail, IsString, MinLength } from 'class-validator';
// Define outside the load function so the adapter can be cached
class ClassValidatorSchema {
@IsString()
@MinLength(2)
name: string = '';
@IsString()
@IsEmail()
email: string = '';
}
const schema = ClassValidatorSchema;
// Defaults should also be defined outside the load function
const defaults = new schema();
export const load = async () => {
// class-validator requires explicit default values for now
const form = await superValidate(classvalidator(schema, { defaults }));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'effect'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { effect } from 'sveltekit-superforms/adapters';
import { Schema } from 'effect';
const emailRegex = /^[^@]+@[^@]+.[^@]+$/;
const schema = Schema.Struct({
name: Schema.String.annotations({ default: 'Hello world!' }),
email: Schema.String.pipe(
Schema.filter((s) => emailRegex.test(s) || 'must be a valid email', {
jsonSchema: { format: 'email' }
})
)
});
export const load = async () => {
const form = await superValidate(effect(schema));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'joi'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { joi } from 'sveltekit-superforms/adapters';
import Joi from 'joi';
// Define outside the load function so the adapter can be cached
const schema = Joi.object({
name: Joi.string().default('Hello world!'),
email: Joi.string().email().required()
});
export const load = async () => {
const form = await superValidate(joi(schema));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'json-schema'}
```ts
import { superValidate, type JSONSchema } from 'sveltekit-superforms';
import { schemasafe } from 'sveltekit-superforms/adapters';
export const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 2, default: 'Hello world!' },
email: { type: 'string', format: 'email' }
},
required: ['name', 'email'],
additionalProperties: false,
$schema: 'http://json-schema.org/draft-07/schema#'
} as const satisfies JSONSchema;
export const load = async () => {
// The adapter must be defined before superValidate for JSON Schema.
const adapter = schemasafe(schema);
const form = await superValidate(request, adapter);
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'superstruct'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { superstruct } from 'sveltekit-superforms/adapters';
import { object, string, defaulted, define } from 'superstruct';
const email = () => define<string>('email', (value) => String(value).includes('@'));
// Define outside the load function so the adapter can be cached
const schema = object({
name: defaulted(string(), 'Hello world!'),
email: email()
});
// Defaults should also be defined outside the load function
const defaults = { name: 'Hello world!', email: '' };
export const load = async () => {
const form = await superValidate(superstruct(schema, { defaults }));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == '@sinclair/typebox'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { typebox } from 'sveltekit-superforms/adapters';
import { Type } from '@sinclair/typebox';
// Define outside the load function so the adapter can be cached
const schema = Type.Object({
name: Type.String({ default: 'Hello world!' }),
email: Type.String({ format: 'email' })
});
export const load = async () => {
const form = await superValidate(typebox(schema));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'valibot'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { valibot } from 'sveltekit-superforms/adapters';
import { object, string, email, optional, pipe, minLength } from 'valibot';
// Define outside the load function so the adapter can be cached
const schema = object({
name: pipe(optional(string(), 'Hello world!'), minLength(2)),
email: pipe(string(), email())
});
export const load = async () => {
const form = await superValidate(valibot(schema));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == '@vinejs/vine'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { vine } from 'sveltekit-superforms/adapters';
import Vine from '@vinejs/vine';
// Define outside the load function so the adapter can be cached
const schema = Vine.object({
name: Vine.string(),
email: Vine.string().email()
});
// Defaults should also be defined outside the load function
const defaults = { name: 'Hello world!', email: '' };
export const load = async () => {
const form = await superValidate(vine(schema, { defaults }));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'yup'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { yup } from 'sveltekit-superforms/adapters';
import { object, string } from 'yup';
// Define outside the load function so the adapter can be cached
const schema = object({
name: string().default('Hello world!'),
email: string().email().required()
});
export const load = async () => {
const form = await superValidate(yup(schema));
// Always return { form } in load functions
return { form };
};
```
{:else if $settings.lib == 'zod'}
```ts
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
// Define outside the load function so the adapter can be cached
const schema = z.object({
name: z.string().default('Hello world!'),
email: z.string().email()
});
export const load = async () => {
const form = await superValidate(zod(schema));
// Always return { form } in load functions
return { form };
};
```
{:else}
> Select a validation library at the top of the page to see the example code.
{/if}
The Superforms server API is called `superValidate`. You can call it in two ways in the load function:
### Empty form
If you want the form to be initially empty, just pass the adapter as in the example above, and the form will be filled with default values based on the schema. For example, a `string` field results in an empty string, unless you have specified a default.
### Populate form from database
If you want to populate the form, usually from a database, you can send data to `superValidate` as the first parameter, adapter second, like this:
```ts
import { error } from '@sveltejs/kit';
export const load = async ({ params }) => {
// Replace with your database
const user = db.users.findUnique({
where: { id: params.id }
});
if (!user) error(404, 'Not found');
const form = await superValidate(user, your_adapter(schema));
// Always return { form } in load functions
return { form };
};
```
As long as the data partially matches the schema, you can pass it directly to `superValidate`. This is useful for backend interfaces, where the form should usually be populated based on a url like `/users/123`.
> Errors will be displayed when the form is populated, but not when empty. You can modify this behavior [with an option](/concepts/error-handling#initial-form-errors).
### Important note about return values
Unless you call the SvelteKit `redirect` or `error` functions, you should **always** return the form object to the client, either directly or through a helper function. The name of the variable doesn't matter; you can call it `{ loginForm }` or anything else, but it needs to be returned like this in all code paths that returns, both in load functions and form actions. If you don't, the form won't be updated with new data (like errors) on the client.
## Posting data
In the form actions, also defined in `+page.server.ts`, we'll use `superValidate` again, but now it should handle `FormData`. This can be done in several ways:
- Use the `request` parameter (which contains `FormData`)
- Use the `event` object (which contains the request)
- Use `FormData` directly, if you need to access it before calling `superValidate`.
The most common is to use `request`:
**src/routes/+page.server.ts**
{#if $settings.lib == 'json-schema'}
```ts
import { message } from 'sveltekit-superforms';
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
// The adapter must be defined before superValidate for JSON Schema.
const adapter = schemasafe(schema);
const form = await superValidate(request, adapter);
console.log(form);
if (!form.valid) {
// Return { form } and things will just work.
return fail(400, { form });
}
// TODO: Do something with the validated form.data
// Return the form with a status message
return message(form, 'Form posted successfully!');
}
};
```
{:else if $settings.lib == 'class-validator' || $settings.lib == 'superstruct' || $settings.lib == 'arktype' || $settings.lib == '@vinejs/vine'}
```ts
import { message } from 'sveltekit-superforms';
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, your_adapter(schema, { defaults }));
console.log(form);
if (!form.valid) {
// Return { form } and things will just work.
return fail(400, { form });
}
// TODO: Do something with the validated form.data
// Return the form with a status message
return message(form, 'Form posted successfully!');
}
};
```
{:else}
```ts
import { message } from 'sveltekit-superforms';
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const form = await superValidate(request, your_adapter(schema));
console.log(form);
if (!form.valid) {
// Return { form } and things will just work.
return fail(400, { form });
}
// TODO: Do something with the validated form.data
// Return the form with a status message
return message(form, 'Form posted successfully!');
}
};
```
{/if}
## For simple forms
If you have a very simple form and no intentions to use any client-side functionality like events, loading spinners, nested data, etc, then you don't have to include the client part, which the rest of the tutorial is about. There's a short example how to display errors and messages without the client [here](/examples?tag=runes). Enjoy the simplicity!
## Displaying the form
The data from `superValidate` is now available in `+page.svelte` as `data.form`, as we did a `return { form }`. Now we can use the client part of the API:
**src/routes/+page.svelte**
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms';
let { data } = $props();
// Client API:
const { form } = superForm(data.form);
</script>
<form method="POST">
<label for="name">Name</label>
<input type="text" name="name" bind:value={$form.name} />
<label for="email">E-mail</label>
<input type="email" name="email" bind:value={$form.email} />
<div><button>Submit</button></div>
</form>
```
The `superForm` function is used to create a form on the client, and `bind:value` is used to create a two-way binding between the form data and the input fields.
> Two notes: There should be only one `superForm` instance per form - its methods cannot be used in multiple forms. And don't forget the `name` attribute on the input fields! Unless you are using [nested data](/concepts/nested-data), they are required.
This is what the form should look like now:
<Form {data} bind:this={formComponent} />
### Debugging
We can see that the form has been populated with the default values from the schema. But let's add the debugging component [SuperDebug](/super-debug) to look behind the scenes:
**src/routes/+page.svelte**
```svelte
<script lang="ts">
import SuperDebug from 'sveltekit-superforms';
</script>
<SuperDebug data={$form} />
```
This should be displayed:
<SuperDebug data={$form} />
When editing the form fields (try in the form above), the data is automatically updated.
SuperDebug also displays a copy button and the current page status in the right corner. There are many [configuration options](/super-debug) available.
## Posting the form
Now we can post the form back to the server. Submit the form, and see what's happening on the server:
```ts
{
id: 'a3g9kke',
valid: false,
posted: true,
data: { name: 'Hello world!', email: '' },
errors: { email: [ 'Invalid email' ] }
}
```
This is the validation object returned from `superValidate`, containing the data needed to update the form:
| Property | Purpose |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| **id** | Id for the schema, to handle [multiple forms](/concepts/multiple-forms) on the same page. |
| **valid** | Tells you whether the validation succeeded or not. Used on the server and in [events](/concepts/events). |
| **posted** | Tells you if the data was posted (in a form action) or not (in a load function). |
| **data** | The posted data, which should be returned to the client using `fail` if not valid. |
| **errors** | An object with all validation errors, in a structure reflecting the data. |
| **message** | (optional) Can be set as a [status message](/concepts/messages). |
There are some other properties as well, only being sent in the load function:
| Property | Purpose |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **constraints** | An object with [HTML validation constraints](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation), that can be spread on input fields. |
| **shape** | Used internally in error handling. |
You can modify any of these, and they will be updated on the client when you `return { form }`. There are a couple of helper functions for making this more convenient, like [message](/concepts/messages) and [setError](/concepts/error-handling).
### Displaying errors
Now we know that validation has failed and there are errors being sent to the client. We display these by adding properties to the destructuring assignment of `superForm`:
**src/routes/+page.svelte**
```svelte
<script lang="ts">
const { form, errors, constraints, message } = superForm(data.form);
// ^^^^^^ ^^^^^^^^^^^ ^^^^^^^
</script>
{#if $message}<h3>{$message}</h3>{/if}
<form method="POST">
<label for="name">Name</label>
<input
type="text"
name="name"
aria-invalid={$errors.name ? 'true' : undefined}
bind:value={$form.name}
{...$constraints.name} />
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
<label for="email">E-mail</label>
<input
type="email"
name="email"
aria-invalid={$errors.email ? 'true' : undefined}
bind:value={$form.email}
{...$constraints.email} />
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
<div><button>Submit</button></div>
</form>
<style>
.invalid {
color: red;
}
</style>
```
By including the `errors` store, we can display errors where appropriate, and through `constraints` we'll get browser validation even without JavaScript enabled.
The `aria-invalid` attribute is used to [automatically focus](/concepts/error-handling#errorselector) on the first error field. And finally, we included the [status message](/concepts/messages) above the form to show if it was posted successfully.
We now have a fully working form, with convenient handling of data and validation both on the client and server!
There are no hidden DOM manipulations or other secrets; it's just HTML attributes and Svelte stores, which means it works perfectly with server-side rendering. No JavaScript is required for the basics.
### Adding progressive enhancement
As a last step, let's add progressive enhancement, so JavaScript users will have a nicer experience. We also need it for enabling [client-side validation](/concepts/client-validation) and [events](/concepts/events), and of course to avoid reloading the page when the form is posted.
This is simply done with `enhance`, returned from `superForm`:
```svelte
<script lang="ts">
const { form, errors, constraints, message, enhance } = superForm(data.form);
// ^^^^^^^
</script>
<!-- Add to the form element: -->
<form method="POST" use:enhance>
```
Now the page won't fully reload when submitting, and we unlock lots of client-side features like timers for [loading spinners](/concepts/timers), [auto error focus](/concepts/error-handling#errorselector), [tainted fields](/concepts/tainted), etc, which you can read about under the Concepts section in the navigation.
The `use:enhance` action takes no arguments; instead, events are used to hook into the SvelteKit use:enhance parameters and more. Check out the [events page](/concepts/events) for details.
## Next steps
This concludes the tutorial! To learn the details, keep reading under the Concepts section in the navigation. A [status message](/concepts/messages) is very common to add, for example. Also, if you plan to use nested data (objects and arrays within the schema), read the [nested data](/concepts/nested-data) page carefully. The same goes for having [multiple forms on the same page](/concepts/multiple-forms).
When you're ready for something more advanced, check out the [CRUD tutorial](/crud), which shows how to make a fully working backend in about 150 lines of code.
Enjoy your Superforms!
---
File: /src/routes/legacy/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
<Head title="Version 0.x and how to migrate" />
# Legacy version
Superforms 1.x is available, so if you're still on version 0.x, please upgrade! No support can be given for the 0.x version.
- [Migration guide 0.x -> 1.0](/migration)
- [What's new in 1.0?](/whats-new-v1)
- [0.x documentation](https://superforms-legacy.vercel.app/)
---
File: /src/routes/migration/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# Migration guide
<Head title="Migration guide from 0.x to 1.0" />
Lists the breaking changes that you need to address to upgrade from v0.x to v1.0.
## Updating
To update, change your `package.json` entry for `sveltekit-superforms` to `^1.0.0`:
```json
{
"devDependencies": {
"sveltekit-superforms": "^1.0.0"
}
}
```
(If you're using [Svelte 4](https://svelte.dev/blog/svelte-4), you'll need at least version `^1.1.2` of Superforms.)
The guide is written with the affected methods in the headlines, so you can scan through this page and apply the changes if you're using them in your code.
### superForm
For type safety reasons, you cannot pass `null`, `undefined`, or arbitrary data to `superForm` anymore. Instead, you should be using `superValidate` to get the initial form object, by returning `{ form }` from a load function and passing it to `superForm`.
Not doing this will result in the following warning:
```
No form data sent to superForm, schema type safety cannot be guaranteed. Also, no constraints will exist for the form.
Set the warnings.noValidationAndConstraints option to false to disable this warning.
```
When this happens, you need to check the call to `superForm` and apply the above fix.
In client-only scenarios, especially in SPA's, you must use `superValidate` or `superValidateSync` before calling `superForm`. Read more on the [SPA page](/concepts/spa) about this.
### valid, empty, firstError
The `$valid`, `$empty`, and `$firstError` stores are removed from the client; they weren't that useful. `$allErrors` can be used instead, together with the new `$posted` store (which shows if the form has been previously posted or not).
`empty` is removed from the object returned from `superValidate`, which type was previously called `Validation` but is now called `SuperValidated`.
### setError
The `setError` function works as before, except that you must use an empty string instead of `null` for form-level errors.
**Note, however**, that `setError` conflicts with client-side validation, the errors will be removed when a Zod schema is used for validation. Therefore, rely on the schema for most validation, if you're using it client-side.
```ts
const schema = z
.object({
password: z.string().min(8),
confirmPassword: z.string()
})
.refine((data) => password == confirmPassword, `Passwords doesn't match.`);
```
The above error set in `refine` will be available on the client as `$errors._errors` as before, and will be automatically removed (or added) during client-side validation.
If you'd like a form-level message to persist, using `message` instead of `setError` will persist it until the next form submission:
```ts
const form = await superValidate(request, zod(schema));
if (!form.valid) return fail(400, { form });
if (form.data.password != form.data.confirmPassword) {
// Stays until form is posted again, regardless of client-side validation
return message(form, `Passwords doesn't match`, { status: 400 });
}
```
Finally, the status option for `setError` (and `message`) must be in the `400-599` range.
### setError (again), validate, proxy functions
`FieldPath` is gone - the above methods are now using a string accessor like `tags[2].id` instead of the previous array syntax `['tags', 2, 'id']`.
```diff
const { form, enhance, validate } = superForm(data.form)
- validate(['tags', i, 'name'], { update: false });
+ validate(`tags[${i}].name`, { update: false });
```
This also applies to generic components. The types have been simplified, so you should change them to this, also described on the [componentization page](/components):
```svelte
<script lang="ts">
import type { z, AnyZodObject } from 'zod';
import type { ZodValidation, FormPathLeaves } from 'sveltekit-superforms';
import { formFieldProxy, type SuperForm } from 'sveltekit-superforms/client';
type T = $$Generic<AnyZodObject>;
export let form: SuperForm<ZodValidation<T>, unknown>;
export let field: FormPathLeaves<z.infer<T>>;
const { value, errors, constraints } = formFieldProxy(form, field);
</script>
```
> The `FormPathLeaves` type is there to prevent using a schema field that isn't at the end of the schema (the "leaves" of the schema tree)
Also note that arrays and objects **cannot** be used in `formFieldProxy`. So if your schema is defined as:
```ts
import { formFieldProxy } from 'sveltekit-superforms/client';
const schema = z.object({
tags: z
.object({
id: z.number(),
name: z.string().min(1)
})
.array()
});
const formData = superForm(data.form);
// This won't work
const tags = formFieldProxy(formData, 'tags');
// Not this either
const tag = formFieldProxy(formData, 'tags[0]');
// But this will work since it's a field at the "end" of the schema
const tagName = formFieldProxy(formData, 'tags[0].name');
```
This only applies to `formFieldProxy`, since it maps to errors and constraints as well as the form. If you want to proxy just a form value, `fieldProxy` will work with any part of the schema.
```ts
import { fieldProxy } from 'sveltekit-superforms/client';
const { form } = superForm(data.form);
const tags = fieldProxy(form, 'tags');
```
### allErrors
The signature for `allErrors` has changed, to make it easier to group related messages:
```diff
- { path: string[]; message: string[] }
+ { path: string; messages: string[] }
```
The path follows the same format as the above-described string accessor path. If you want to display all messages grouped:
```svelte
{#if $allErrors.length}
<ul>
{#each $allErrors as error}
<li>
<b>{error.path}:</b>
{error.messages.join('. ')}.
</li>
{/each}
</ul>
{/if}
```
Or as before, separate for each error:
```svelte
{#if $allErrors.length}
<ul>
{#each $allErrors as error}
{#each error.messages as message}
<li>
<b>{error.path}:</b>
{message}.
</li>
{/each}
{/each}
</ul>
{/if}
```
### defaultData
The undocumented `defaultData` function is now called `defaultValues`. You can use this to get the default values for a schema.
```diff
- import { defaultData } from 'sveltekit-superforms/server`
+ import { defaultValues } from 'sveltekit-superforms/server`
```
See [the API](/api#defaultvaluesschema) for documentation.
### message, setMessage
The `valid` option is removed from `message/setMessage`; any status >= 400 will return a fail. As with `setError`, the status code must be in the `400-599` range.
### meta
The virtually unused `meta` store, which containted some basic metadata about the schema, has been removed. Use the Zod schema directly instead for reflection.
## Client options
The following `superForm` options have changed:
### resetForm
Resetting the form now works without `use:enhance` and without JavaScript! Just set the `resetForm` option to `true`.
If you have used the function version of `resetForm`, `() => boolean`, it is now synchronous.
### errorSelector
The default `errorSelector` is now `[aria-invalid="true"],[data-invalid]`, so if you want to be more accessibility-friendly:
```diff
<input
name="name"
bind:value={$form.name}
- data-invalid={$errors.name}
+ aria-invalid={$errors.name ? 'true' : undefined}
/>
```
## Server options
The following `superValidate` options have changed:
### noErrors
`noErrors` is removed from the options. Use `errors` instead to determine if errors should be added or not to the validation.
```ts
// Add errors to an empty form
const form = await superValidate(zod(schema), { errors: true });
```
The [1.0 release notes](https://github.com/ciscoheat/sveltekit-superforms/releases/tag/v1.0.0) have a full list of changes, and as usual, let me know on Github or Discord if something is unclear or not working.
---
File: /src/routes/migration-v2/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import fileDebug from './file-debug.png'
import Installer from './Installer.svelte'
</script>
# Superforms v2 - Migration guide
<Head title="Superforms v2 - Migration guide" />
Version 2 is a big upgrade, because it now has the potential to **support virtually every validation library out there**. Of course, Zod is still perfectly usable with just a small modification to the code.
## Changes
Here's a brief list of the changes, keep reading further down for details.
### The biggest change (IMPORTANT)
The biggest breaking change is that two options now follow the SvelteKit defaults more closely:
- resetForm is now `true` as default
- taintedMessage is now `false` as default
But don't worry, there's no need to change these options on every form to migrate. Instead, add the following define in `vite.config.ts` to keep the original behavior:
```diff
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
},
+ define: {
+ SUPERFORMS_LEGACY: true
+ }
});
```
You can do the same on a form-by-form basis by setting the `legacy` option on `superForm` to `true` as well.
> When legacy mode is set and you want to use the new [file uploads](/concepts/files) feature, you need to add `{ allowFiles: true }` as an option to `superValidate` in form actions.
### superValidate
Instead of a Zod schema, you now use an adapter for your favorite validation library. The following are currently supported:
| Library | Adapter | Requires defaults |
| -------- | --------------------------------------------------------- | ----------------- |
| Arktype | `import { arktype } from 'sveltekit-superforms/adapters'` | Yes |
| Joi | `import { joi } from 'sveltekit-superforms/adapters'` | No |
| TypeBox | `import { typebox } from 'sveltekit-superforms/adapters'` | No |
| Valibot | `import { valibot } from 'sveltekit-superforms/adapters'` | No |
| VineJS | `import { vine } from 'sveltekit-superforms/adapters'` | Yes |
| Yup | `import { yup } from 'sveltekit-superforms/adapters'` | No |
| Zod | `import { zod } from 'sveltekit-superforms/adapters'` | No |
With the library installed and the adapter imported, all you need to do is wrap the schema with it:
```ts
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
const form = await superValidate(zod(schema));
```
The libraries in the list that requires defaults don't have full introspection capabilities (yet), in which case you need to supply the default values for the form data as an option:
```ts
import { type } from 'arktype';
// Arktype schema, powerful stuff
const schema = type({
name: 'string',
email: 'email',
tags: '(string>=2)[]>=3',
score: 'integer>=0'
});
const defaults = { name: '', email: '', tags: [], score: 0 };
export const load = async () => {
const form = await superValidate(arktype(schema, { defaults }));
return { form };
};
```
#### Schema caching
In the example above, both the schema and the defaults are defined outside the load function, on the top level of the module. **This is very important to make caching work.** The adapter is memoized (cached) with its arguments, so they must be kept in memory. Therefore, define the schema, options and potential default values for the adapter on the top level of a module, so they always refer to the same object.
#### Optimized client-side validation
The client-side validation is using the smallest possible part of the adapter, to minimize the bundle size for the client. To use it, append `Client` to the adapter import, for example:
```ts
import { valibotClient } from 'sveltekit-superforms/adapters';
import { schema } from './schema.js';
const { form, errors, enhance, options } = superForm(data.form, {
validators: valibotClient(schema)
});
```
> This works with the same schema as the one used on the server. If you need to switch schemas on the client (with `options.validators`), you need the full adapter.
The built-in Superforms validator is now deprecated, since it requires you to do much of the type checking yourself. To keep using it, import `superformClient` and use the new `Infer` type to type it correctly with the schema, as in the following example. The input parameter can now be `undefined` as well, be sure to check for that case.
```ts
import type { Infer } from 'sveltekit-superforms';
import type { schema } from './schema.js';
import { superformClient } from 'sveltekit-superforms/adapters';
const { form, errors, enhance } = superForm(data.form, {
validators: superformClient<Infer<typeof schema>>({
name: (name?) => {
if(!name || name.length < 2) return 'Name must be at least two characters'
}
})
});
```
As said, this adapter requires you to do much of the error-prone type checking manually, so in general it is **not** a replacement for the other validation libraries. Use it only for a very good reason!
### SuperValidated type parameters have changed
If you have used type parameters for a call to `superValidate` before, or have been using the `SuperValidated` type, you now need to wrap the schema parameter with `Infer`:
```ts
import type { Infer } from 'sveltekit-superforms'
import { zod } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
type Message = { status: 'success' | 'failure', text: string }
const form = await superValidate<Infer<typeof schema>, Message>(zod(schema));
```
```ts
import type { LoginSchema } from '$lib/schemas';
import type { Infer } from 'sveltekit-superforms'
let { data } : { data: SuperValidated<Infer<LoginSchema>> } = $props();
```
If your schema uses transformations or pipes, so the input and output types are different, there's an `InferIn` type and a third type parameter that can be used.
```ts
import type { Infer, InferIn } from 'sveltekit-superforms'
import { zod } from 'sveltekit-superforms/adapters'
import { schema } from './schema.js'
type Message = { status: 'success' | 'failure', text: string }
type Validated = SuperValidated<Infer<typeof schema>, Message, InferIn<typeof schema>>;
const form : Validated = await superValidate(zod(schema));
```
Also, `constraints` are now optional in the `SuperValidated` type, since they won't be returned when posting data anymore, only when loading data, to save some bandwidth. This is only relevant if you're changing the constraints before calling `superForm`.
### superValidateSync is renamed to defaults
The quite popular `superValidateSync` function has changed, since it's not possible to make a synchronous validation anymore (not all validation libaries are synchronous). So if you've validated data with `superValidateSync` (in its first parameter), be aware that **superValidateSync cannot do validation anymore**. You need to use a `+page.ts` to do proper validation, as described on the [SPA page](/concepts/spa#using-pagets-instead-of-pageserverts).
Since this is a bit of a security issue, `superValidateSync` has been renamed to `defaults`.
Fortunately though, a [quick Github search](https://github.com/search?q=superValidateSync%28&type=code) reveals that most of its usages are with the schema only, which requires no validation and no `+page.ts`. In that case, just call `defaults` with your adapter and potential initial data, and you're good to go:
```ts
import { defaults } from 'sveltekit-superforms'
// Getting the default values from the schema:
const { form, errors, enhance } = superForm(defaults(zod(schema)), {
SPA: true,
validators: zod(schema),
// ...
})
```
```ts
import { defaults } from 'sveltekit-superforms'
// Supplying initial data (can be partial, won't be validated)
const initialData = { name: 'New user' }
const { form, errors, enhance } = superForm(defaults(initialData, zod(schema)), {
SPA: true,
validators: zod(schema),
// ...
})
```
Note that `superValidate` can be used anywhere but on the top-level of Svelte components, so it's not completely removed from the client and SPA usage. But client-side validation is more of a convenience than ensuring data integrity. Always let an external API or a server request do a proper validation of the data before it's stored or used somewhere.
### validate method with no arguments is renamed to validateForm
Previously, you could do `const result = await validate()` to get a validation result for the whole form. This overload caused a lot of typing issues, so it has now been split into `validate` for fields, and `validateForm` for the whole form. Just replace the calls to `validate()` with `validateForm()` to fix this.
### id option must be a string
It's not possible to set the `id` option to `undefined` anymore, which is very rare anyway. By default, the id is automatically set to a string hash of the schema. It's only for multiple forms on the same page, or dynamically generated schemas, that you may want to change it.
### arrayProxy
A simple change: `fieldErrors` is renamed to `valueErrors`.
### intProxy and numberProxy
The `emptyIfZero` setting is removed from `numberProxy` and `intProxy`.
### The defaultValidators option has moved
Another simple change: If you've been using `defaultValidators`, set the value `'clear'` on the `validators` option instead.
### Enums in schemas
Previously, it was possible to post the name of the enum as a string, even if it was a numeric enum. That's not possible anymore:
```ts
// Cannot post the string "Delayed" and expect it to be parsed as 2 anymore.
enum FetchStatus {
Idle = 0,
Submitting = 1,
Delayed = 2,
Timeout = 3
}
```
For string enums, it works to post strings, of course.
#### Enums must have an explicit default value
Enums don't have a default "empty" value unlike other types, so it's not certain what the default value should be. To be able to set an enum as required, the first enum value will be used, unless there is an explicit default.
```ts
export enum Foo {
A = 2,
B = 3
}
const schema = z.object({
foo: z.nativeEnum(Foo), // Default is Foo.A, field is required
zodEnum: z.enum(['a', 'b', 'c']).default('b') // Explicit default 'b', field is optional
})
```
### Use isTainted to check tainted status
A new `superForm.isTainted` method is available, to check whether any part of the form is tainted. Use it instead of checking the `$tainted` store, which may give unexpected results.
```ts
const { form, enhance, isTainted } = superForm(form.data);
// Check the whole form
if(isTainted())
// Check a part of the form
if(isTainted('name'))
```
Speaking of tainted, it now keeps track of the original data, so if you go back to a previous value, it's not considered tainted anymore.
### Schema/validation changes
The underlying data model for Superforms is now [JSON Schema](https://json-schema.org/), which is what makes it possible to support all the validation libraries. Some changes had to be made for this to work:
#### No side-effects for default values.
If no data is sent to `superValidate`, and no errors should be displayed, as is default in the load function:
```ts
const form = await superValidate(zod(schema));
```
Then the default values won't be parsed with the schema. In other words, no side-effects like `z.refine` will be executed. If you need initial validation of even the default data, set the `errors` option to `true`, and optionally clear the errors after validation:
```ts
const form = await superValidate(zod(schema), { errors: true });
form.errors = {}
```
#### Fields with default values aren't required anymore
In hindsight, this should have been the default, given the forgiving nature of the data coercion and parsing. When a default value exists, the field is not required anymore. If that field isn't posted, its default value will be added to `form.data`.
### Components
Generic components were previously using Zod types for type safety. It is simpler now:
**TextInput.svelte**
```svelte
<script lang="ts" context="module">
type T = Record<string, unknown>;
</script>
<script lang="ts" generics="T extends Record<string, unknown>">
import { formFieldProxy, type SuperForm, type FormPathLeaves } from 'sveltekit-superforms';
export let superform: SuperForm<T>;
export let field: FormPathLeaves<T>;
export let label = '';
const { value, errors, constraints } = formFieldProxy(superform, field);
</script>
<label>
{#if label}{label}<br />{/if}
<input
name={field}
type="text"
aria-invalid={$errors ? 'true' : undefined}
bind:value={$value}
{...$constraints}
{...$$restProps}
/>
{#if $errors}<span class="invalid">{$errors}</span>{/if}
</label>
```
**+page.svelte**
```svelte
<script lang="ts">
import { superForm } from 'sveltekit-superforms';
import TextInput from './TextInput.svelte';
export let data;
const superform = superForm(data.form);
const { enhance } = superform;
</script>
<form method="POST" use:enhance>
<TextInput {superform} field="name" />
<button>Submit</button>
</form>
```
## Update your imports, in case of problems
The `/client` and `/server` paths for imports aren't needed anymore, but are kept for backwards compatibility. If you're having problems with loading pages, import everything except adapters from `sveltekit-superforms`. The same goes for `SuperDebug`, which is now the default export of the library.
```ts
import { superForm, superValidate, dateProxy } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import SuperDebug from 'sveltekit-superforms';
```
## Removed features
### superForm.fields is removed
The `fields` object returned from `superForm` was an inferior version of [formFieldProxy](/api#formfieldproxysuperform-fieldname-options), and has now been removed. Use `formFieldProxy` to create your own instead.
### superForm does not support untainting specific fields anymore
You could previously choose what specific fields to untaint with a `fields` option, when updating `$form`. It was a rarely used feature that has now been removed.
### onError "message" parameter is removed
Previously, there was a `message` parameter in the onError event. It's gone now, since it was pointing to the message store, and you might as well just assign it directly:
```ts
const { form, message, enhance } = superForm(data.form, {
onError({ result }) {
$message = result.error.message
}
})
```
### flashMessage.onError "message" parameter renamed to "flashMessage"
To be more consistent with the message parameter, the rarely used `flashMessage` option in `superForm` has an `onError` event with a `message` parameter, but it is now renamed to `flashMessage` to signify which message can actually be updated.
## New features
Of course, there are also new features, so the migration will be worthwhile. Check the [what's new](/whats-new-v2) page for more information.
---
File: /src/routes/rate-limiting/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# Rate limiting
<Head title="Rate limiting with sveltekit-rate-limiter" />
Superforms has a basic [client-side prevention](/concepts/submit-behavior#multiplesubmits) of multiple form submissions. But you may want to limit the rate of form submissions on the server as well, to prevent misuse and spamming.
A useful library for this is [sveltekit-rate-limiter](https://github.com/ciscoheat/sveltekit-rate-limiter), which makes it easy to rate limit password resets, account registration, etc. It not only works with forms but all requests, so API limiting is another use for it. With this library, basic rate limiting isn't much harder than:
```ts
import { error } from '@sveltejs/kit';
import { RateLimiter } from 'sveltekit-rate-limiter/server';
const limiter = new RateLimiter({
IP: [10, 'h'], // IP address limiter
IPUA: [5, 'm'], // IP + User Agent limiter
});
export const actions = {
default: async (event) => {
// Every call to isLimited counts as a hit towards the rate limit for the event.
if (await limiter.isLimited(event)) error(429);
}
};
```
It can be easily expanded with custom limiter plugins and different stores than the built-in ones.
Installation and usage instructions are available at its Github repo: https://github.com/ciscoheat/sveltekit-rate-limiter
---
File: /src/routes/sponsors/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Sponsor from '$lib/sponsoring/Sponsor.svelte'
import Message from '$lib/sponsoring/Message.svelte'
import Sponsors from './Sponsors.svelte'
</script>
<Message />
<Head title="Super Sponsors" />
# Super Sponsors
Here are the kind individuals and organizations that are donating $10 or more monthly to the future development and support of Superforms!
<Sponsors />
To them and all of you that have donated a one-time amount - **Thank you**, it's so appreciated. ❤️ Whenever I have time, I will reach out and say thank you personally.
## Donate to be a sponsor
Any $10 or more monthly donation will be listed on this page with a picture and link.
<Sponsor />
---
File: /src/routes/super-debug/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import CssVars from './CssVars.svelte'
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'
import { writable } from 'svelte/store'
import Input from './Input.svelte'
import { onMount } from 'svelte'
import { page } from '$app/stores'
import { dev } from '$app/environment'
const form = writable({
name: 'Gaspar Soiaga',
email: '[email protected]',
birth: new Date('1649-01-01')
});
const errors = writable({
email: ['Cannot use example.com as email.'],
});
const fileData = { file: new File(['Example file'.repeat(10)], 'example.txt', { type: 'text/plain' }) };
let product = undefined;
let rejected = undefined;
onMount(() => {
product = new Promise(resolve => {
fetch('https://dummyjson.com/products/1').then(async response => {
await new Promise(res => setTimeout(res, 2000))
resolve(response.json()
)})
})
rejected = new Promise(async (_, reject) => {
setTimeout(() => reject(new Error('Broken promise')), 2000)
})
})
</script>
<Head title="SuperDebug - the Super debugging component" />
# SuperDebug
`SuperDebug` is a debugging component that gives you colorized and nicely formatted output for any data structure, usually `$form` returned from `superForm`. It also shows the current page status in the top right corner.
It's not limited to the Superforms data, other use cases includes debugging plain objects, promises, stores and more.
## Usage
```ts
import SuperDebug from 'sveltekit-superforms';
```
```svelte
<SuperDebug data={$form} />
```
## Props reference
```svelte
<SuperDebug
data={any}
display={true}
status={true}
label=''
collapsible={false}
collapsed={false}
stringTruncate={120}
raw={false}
functions={false}
theme='default'
ref={HTMLPreElement}
/>
```
| Prop | Type | Default value | Description |
| ------------------ | -------------- | ------------- | ----------- |
| **data** | any | | Data to be displayed by SuperDebug. The only required prop. |
| **display** | boolean | `true` | Whether to show or hide SuperDebug. |
| **status** | boolean | `true` | Whether to show or hide the HTTP status code of the current page. |
| **label** | string | `""` | Add a label to SuperDebug, useful when using multiple instances on a page. |
| **collapsible** | boolean | `false` | Makes the component collapsible on a per-route basis. |
| **collapsed** | boolean | `false` | If the component is `collapsible`, sets it to initially collapsed. |
| **stringTruncate** | number | `120` | Truncate long string field valuns of the data prop. Set to `0` to disable truncating. |
| **raw** | boolean | `false` | Skip promise and store detection when `true`. |
| **functions** | boolean | `false` | Enables the display of fields of the data prop that are functions. |
| **theme** | "default", "vscode" | `"default"` | Display theme, which can also be customized with CSS variables. |
| **ref** | HTMLPreElement | | Reference to the pre element that contains the data. |
## Examples
<Input {form} />
### Default output
```svelte
<SuperDebug data={$form} />
```
<SuperDebug data={$form} />
### With a label
A label is useful when using multiple instance of SuperDebug.
```svelte
<SuperDebug label="Useful label" data={$form} />
```
<SuperDebug label="Useful label" data={$form} />
### With label, without status
```svelte
<SuperDebug label="Sample User" status={false} data={$form} />
```
<SuperDebug label="Sample User" status={false} data={$form} />
### Without label and status
```svelte
<SuperDebug data={$form} status={false} />
```
<SuperDebug data={$form} status={false} />
### Display only in dev mode
```svelte
<script lang="ts">
import { dev } from '$app/environment';
</script>
<SuperDebug data={$form} display={dev} />
```
<SuperDebug data={$form} display={dev} />
### Promise support
To see this in action, scroll to the product data below and hit refresh.
```ts
// +page.ts
export const load = (async ({ fetch }) => {
const promiseProduct = fetch('https://dummyjson.com/products/1')
.then(response => response.json())
return { promiseProduct }
})
```
```svelte
<SuperDebug label="Dummyjson product" data={data.promiseProduct} />
```
<SuperDebug label="Dummyjson product" data={product} />
### Rejected promise
```ts
// +page.ts
export const load = (async ({ fetch }) => {
const rejected = Promise.reject(throw new Error('Broken promise'))
return { rejected }
})
```
```svelte
<SuperDebug data={rejected} />
```
<SuperDebug data={rejected} />
### Composing debug data
If you want to debug multiple stores/objects in the same instance.
```svelte
<SuperDebug data={{$form, $errors}} />
```
<SuperDebug data={{$form, $errors}} />
### Displaying files
SuperDebug displays `File` and `FileList` objects as well:
```svelte
<SuperDebug data={$form} />
```
<SuperDebug data={fileData} />
### SuperDebug loves stores
You can pass a store directly to SuperDebug:
```svelte
<SuperDebug data={form} />
```
<SuperDebug data={form} />
### Custom styling
```svelte
<SuperDebug
data={$form}
theme="vscode"
--sd-code-date="lightgreen"
/>
```
<SuperDebug
data={$form}
theme="vscode"
--sd-code-date="lightgreen"
/>
#### CSS variables available for customization
<CssVars />
Note that styling the component produces the side-effect described in the [Svelte docs](https://svelte.dev/docs/component-directives#style-props).
### Page data
Debugging Svelte's `$page` data, when the going gets tough. Since it can contain a lot of data, using the `collapsible` prop is convenient.
```svelte
<script lang="ts">
import { page } from '$app/stores';
</script>
<SuperDebug label="$page data" data={$page} collapsible />
```
<SuperDebug label="$page data" data={$page} collapsible />
---
File: /src/routes/support/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Sponsor from '$lib/sponsoring/Sponsor.svelte'
import Message from '$lib/sponsoring/Message.svelte'
</script>
<Message />
<Head title="Help and support for Superforms" />
# Help & Support
There are two ways of getting support with using Superforms:
## Free support
If you're using Superforms in a personal or non-profit project, support is completely free; a star on [Github](https://github.com/ciscoheat/sveltekit-superforms) is more than enough if you want to show your appreciation. Join [#free-support](https://discord.gg/8X9Wfb2wbz) on Discord and ask away!
## Commercial support
If you're making or aiming to make money on your project, a donation proportional to the current profit of the project or the company you work for, will give you a month of commercial support. Donate with one of the options below, then ask in the [#commercial-support](https://discord.gg/m6hUXE4eNQ) channel on Discord, or [on Github](https://github.com/ciscoheat/sveltekit-superforms/issues).
<Sponsor />
For a longer period, you can make a monthly donation. Any $10 or higher monthly donation will be listed on the [Sponsors](/sponsors) page with a picture and link, and on Discord as a Sponsor. Many thanks if you want to support Open Source Software in this way!
## Bug reporting
Please report bugs as an issue [on Github](https://github.com/ciscoheat/sveltekit-superforms/issues).
Fixing bugs and working on new features will be handled in due time, but if you want to speed up the process, a donation is welcome, or you can submit a [pull request](https://github.com/ciscoheat/sveltekit-superforms/pulls), since with open source, we can also pay with our time.
---
File: /src/routes/whats-new-v1/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
</script>
# v1.0 - What's new?
<Head title="What's new in 1.0" />
After a lot of work, Superforms 1.0 has been released! Here are the most important new features and improvements.
## Automatic form id
Setting a form `id` for multiple forms on the same page is not required anymore when using `use:enhance`.
```diff
const loginForm = await superValidate(loginSchema, {
- id: 'loginForm'
});
const registerForm = await superValidate(registerSchema, {
- id: 'registerForm'
});
return { loginForm, registerForm }
```
The one exception is if the forms are using the same schema (identical content), then you'll need an id:
```ts
const form1 = await superValidate(schema, { id: 'form1' });
const form2 = await superValidate(schema, { id: 'form2' });
return { form1, form2 };
```
Multiple forms without `use:enhance` work as well, but an id must be manually specified, either as an option, or in a hidden form field called `__superform_id`.
For extra safety, a warning will be emitted if identical id's are detected.
## defaultValues
This method will provide you with the default values for a schema.
```ts
import { defaultValues } from 'sveltekit-superforms/server';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2),
tags: z.string().min(1).array().default(['a', 'b'])
});
// Returns { name: '', tags: ['a', 'b'] }
const defaults = defaultValues(schema);
// Which is the same as form.data in superValidate
// when called with only the schema:
const form = await superValidate(schema);
```
This was previously an undocumented function called `defaultData`. If you've used it, rename it to `defaultValues`.
## superValidateSync
When using `superValidate` on the client, you previously had to use a `+page.ts` file to call `superValidate`, since it is asynchronous. Now you can import `superValidateSync` and use it in components directly (which assumes that there is no async validation in the schema). It can be very convenient in SPA's, just be aware that the client bundle size will increase a bit compared to using `superValidate` only on the server.
```svelte
<script lang="ts">
import { schema } from '$lib/schemas';
import { superValidateSync, superForm } from 'sveltekit-superforms/client';
// Same as returning { form } in a load function
const form = superForm(superValidateSync(schema));
</script>
```
## String path accessors
For errors and proxies with nested data, the array syntax was a bit clunky. It has now been replaced with a typesafe string path, so you can write it just as you would access an object property in normal JS:
```diff
import { setError } from 'sveltekit-superforms/server'
const i = 1;
- setError(form, ['tags', i, 'name'], 'Incorrect name');
+ setError(form, `tags[${i}].name`, 'Incorrect name');
```
```diff
import { intProxy } from 'sveltekit-superforms/client'
const { form } = superForm(data.form);
- const idProxy = intProxy(form, ['user', 'profile', 'id']);
+ const idProxy = intProxy(form, 'user.profile.id');
```
## New 'posted' store
You can now test if the form has been previously posted by deconstructing the boolean `$posted` store from `superForm`.
## Extra options for reset
You can now use the `data` and `id` options when calling reset, to reset the form to different data and id than the initial one. `data` can be partial.
## Better empty value support for proxies
`intProxy`, `numberProxy`, `dateProxy`, and `stringProxy` now have an `empty` option, so empty values can be automatically set to `null` or `undefined`.
## Validate the whole form on the client
The `validate` function can be used to validate a specific field in the form, but now you can also call validate with no arguments, and get a validation result back for the whole form.
## Errors for arrays and objects
Previously, it wasn't possible to handle errors for arrays in the schema, like a minimum or maximum number of items. Now it's possible:
```ts
const schema = z.object({
tags: z.string().array().max(3)
});
const { form, errors } = superForm(data.form);
```
```svelte
{#if $errors.tags._errors}
{$errors.tags._errors}
{/if}
```
The [1.0 release notes](https://github.com/ciscoheat/sveltekit-superforms/releases/tag/v1.0.0) have a full list of changes for 1.0, and as usual, let me know on Github or Discord if something is unclear or not working.
---
File: /src/routes/whats-new-v2/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import fileDebug from './file-debug.png'
</script>
# Version 2 - What's new?
<Head title="What's new in Superforms version 2" />
Superforms 2 has finally been released! Here's a presentation of the new features and improvements:
## File upload support!
Finally, it's possible to handle files with Superforms, even with validation on the client. See the dedicated [file uploads section](/concepts/files) for more information.
## SuperDebug
Now that file uploads is a feature, SuperDebug displays file objects properly:
<img src={fileDebug} alt="SuperDebug displaying a File" />
## Union support
An oft-requested feature has been support for unions, which has always been a bit difficult to handle with `FormData` parsing and default values. But unions can now be used in schemas, with a few compromises:
### Unions must have an explicit default value
If a schema field can be more than one type, it's not possible to know what default value should be set for it. Therefore, you must specify a default value for unions explicitly:
```ts
const schema = z.object({
undecided: z.union([z.string(), z.number()]).default(0)
})
```
### Multi-type unions can only be used when dataType is 'json'
Unions are also quite difficult to make assumptions about in `FormData`. If `"123"` was posted (as all posted values are strings), should it be parsed as a string or a number in the above case?
There is no obvious answer, so unions **with more than one type** can only be used when the `dataType` option is set to `'json'` (which will bypass the whole `FormData` parsing by serializing the form data).
## Form is reset by default
To better follow the SvelteKit defaults, the `resetForm` option for `superForm` is now `true` as default.
## Tainted updates
The default for `taintedMessage` changed too, it is now `false`, so no message will be displayed if the form is modified, unless you set it to either `true`, a string message, or a function that returns a promise resolved to `true` if navigation should proceed (so you can now use a custom dialog for displaying the message).
The tainted store is also smarter, keeping track of the original data, so if you go back to a previous value, it's not considered tainted anymore.
### New isTainted method
A new `isTainted` method is available on `superForm`, to check whether any part of the form is tainted. Use it instead of testing against the `$tainted` store, which may give unexpected results.
```svelte
<script lang="ts">
const { form, enhance, tainted, isTainted } = superForm(form.data);
// Check the whole form
if(isTainted()) {
console.log('The form is tainted')
}
// Check a part of the form
if(isTainted('name')) {
console.log('The name field is tainted')
}
</script>
<!-- Make the function reactive by passing the $tainted store -->
<button disabled={!isTainted($tainted)}>Submit</button>
<!-- It even works with individual fields -->
<button disabled={!isTainted($tainted.name)}>Submit name</button>
```
## onChange event
An `onChange` event is added to the `superForm` options, so you can track specific fields for changes. Check the [events page](/concepts/events#onchange) for the details.
## New validateForm method
Previously you could call `validate()` for retrieving a validation result for the whole form, but you must now use `validateForm()`. There are two options, `{ update?: true, schema?: ValidationAdapter<Partial<T>> }` which can be used to trigger a full client-side validation, and validate the schema partially, which is useful for multi-step forms.
## empty: 'zero' option for intProxy and numberProxy
For number fields, a UX problem has been that the default value for numbers, `0`, hides the placeholder text, and it's not always wanted to have a number there initially. But it's now possible to make this work, with two new options in `intProxy` and `numberProxy`:
```ts
const schema = z.object({
num: z.number()
})
const proxy = intProxy(form, 'num', { empty: 'zero', initiallyEmptyIfZero: true })
```
The `empty` option, if set to `'zero'`, will set the field to 0 if it's empty, and the unfortunately long `initiallyEmptyIfZero` will ensure the field is empty at first.
## Simplified imports
You may have noticed in the examples that `/client` and `/server` isn't needed anymore. Simply import everything except adapters from `sveltekit-superforms`. The same goes for `SuperDebug`, which is now the default export of the library:
```ts
import { superForm, superValidate, dateProxy } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import SuperDebug from 'sveltekit-superforms';
```
# Migrate now!
Read the detailed [migration guide](/migration-v2) to convert your projects to Superforms version 2. Ask any migration questions in the [#v2-migration](https://discord.gg/4mKwqnu25f) Discord channel, or open an issue on [Github](https://github.com/ciscoheat/sveltekit-superforms/issues)
# Release notes
The [2.0 release notes](https://github.com/ciscoheat/sveltekit-superforms/releases/tag/v2.0.0) have a full list of changes.
---
File: /src/routes/+page.md
---
<script lang="ts">
import Head from '$lib/Head.svelte'
import Header from './Header.svelte'
import Youtube from '$lib/Youtube.svelte'
import Gallery from './Gallery.svelte'
import Libraries from '$lib/LibrariesButtons.svelte'
import bugbug from '$lib/assets/bugbug-yellow.svg'
</script>
<Head />
<Header />
Superforms is a SvelteKit form library that brings you a comprehensive solution for **server and client form validation**. It supports a multitude of validation libraries:
<Libraries url="/get-started/" />
Pick your favorite, Superforms takes care of the rest with consistent handling of form data and validation errors, with full type safety. It works with both TypeScript and JavaScript, even in static and single-page apps.
The API is minimal, basically a single method on the server and client, but it's very flexible and configurable to handle every possible case of:
<Gallery />
## Get started
Click [here to get started](/get-started) right away, or watch this video for an introduction to what's possible with Superforms:
<Youtube id="MiKzH3kcVfs" />
<br><br><br>
<div class="flex flex-col items-center">
<div class="text-gray-500 mb-4">Browser testing by</div>
<a href="https://bugbug.io/"><img class="w-36 m-0 p-0" src={bugbug}></a>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment