Actions de formulaire
Un fichier +page.server.js peut exporter des actions, ce qui vous permet d’envoyer des données
avec POST vers le serveur en utilisant un élément de formulaire <form>.
Lorsque vous utilisez un élément <form>, la présence de JavaScript côté client est optionnelle,
mais vous pouvez facilement améliorer progressivement vos interactions de formulaire avec
JavaScript pour fournir la meilleure expérience utilisateur.
Actions par défaut
Dans le cas le plus simple, une page déclare une action par défaut appelée default :
/** @satisfies {import('./$types').Actions} */
export const const actions: {
default: (event: any) => Promise<void>;
}
actions = {
default: (event: any) => Promise<void>default: async (event: anyevent) => {
// TODO identifier l'utilisateur
}
};import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>default: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO identifier l'utilisateur
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;Pour invoquer cette action depuis la page /login, ajouter simplement un élément <form> — aucun
JavaScript n’est nécessaire :
<form method="POST">
<label>
Email
<input name="email" type="email">
</label>
<label>
Mot de passe
<input name="password" type="password">
</label>
<button>Se connecter</button>
</form>Lorsque le bouton est cliqué, le navigateur va envoyer au serveur les données du formulaire via une
requête POST, en exécutant l’action par défaut.
Les actions utilisent toujours des requêtes
POST, puisque les requêtesGETne sont pas censées avoir d’effets de bord.
Nous pouvons aussi invoquer l’action depuis d’autres pages (par exemple, s’il y a un widget de
connexion dans la barre de navigation du layout racine) en ajoutant l’attribut action pointant
vers la page :
<form method="POST" action="/login">
<!-- contenu -->
</form>Actions nommées
Plutôt qu’une seule action default par défaut, une page peut avoir autant d’actions nommées que
nécessaire :
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: (event: any) => Promise<void>;
register: (event: any) => Promise<void>;
}
actions = {
default: async (event) => {
login: (event: any) => Promise<void>login: async (event: anyevent) => {
// TODO connecter l'utilisateur
},
register: (event: any) => Promise<void>register: async (event: anyevent) => {
// TODO inscrire l'utilisateur
}
};import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
default: async (event) => {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>login: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO connecter l'utilisateur
},
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;Pour invoquer une action nommée, ajoutez un paramètre de requête comportant le nom de l’action
préfixé par un caractère / :
<form method="POST" action="?/register"><form method="POST" action="/login?/register">Similaire à l’attribut action, nous pouvons utiliser l’attribut formaction sur un bouton pour
envoyer avec POST les mêmes données de formulaire à une action différente de celle de l’élément
<form> parent :
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Mot de passe
<input name="password" type="password">
</label>
<button>Se connecter</button>
<button formaction="?/register">S'inscrire</button>
</form>Il n’est pas possible d’avoir des actions par défaut dans le même fichier que des actions nommées, car si vous ciblez une action nommée avec POST sans redirection, le paramètre de requête est persisté dans l’URL, ce qui signifie que la prochaine requête POST par défaut va cibler l’action nommée précédente.
Anatomie d’une action
Chaque action reçoit un objet RequestEvent, vous permettant de lire les données avec
request.formData(). Après le traitement de la requête (par exemple, pour connecter l’utilisateur
ou l’utilisatrice en définissant un cookie), l’action peut répondre avec des données qui seront
disponibles via la propriété form sur la page correspondante, et via page.form dans toute
l’application, jusqu’à sa prochaine mise à jour.
import * as module "$lib/server/db"db from '$lib/server/db';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>load({ cookies: CookiesGet or set cookies related to the current request
cookies }) {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return { user: anyuser };
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
};import * as module "$lib/server/db"db from '$lib/server/db';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = async ({ cookies: CookiesGet or set cookies related to the current request
cookies }) => {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return { user: anyuser };
};
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;<script>
/** @type {import('./$types').PageProps} */
let { data, form } = $props();
</script>
{#if form?.success}
<!-- ce message est éphémère ; il existe car la page a été rendu en réponse à une soumission
de formulaire. il disparaîtra si l'utilisateur recharge la page -->
<p>Connexion réussie ! Content de vous revoir, {data.user.name}</p>
{/if}<script lang="ts">
import type { PageProps } from './$types';
let { data, form }: PageProps = $props();
</script>
{#if form?.success}
<!-- ce message est éphémère ; il existe car la page a été rendu en réponse à une soumission
de formulaire. il disparaîtra si l'utilisateur recharge la page -->
<p>Connexion réussie ! Content de vous revoir, {data.user.name}</p>
{/if}Legacy mode
PagePropsa été ajouté dans la version 2.16.0 de SvelteKit. Dans les versions antérieures, vous deviez typer les propriétésdataetformindividuellement :+page/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ let {let data: anydata,let form: anyform } =function $props(): any namespace $props$props();Declares the props that a component accepts. Example:
let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();import type {import PageDataPageData,import ActionDataActionData } from './$types'; let {let data: PageDatadata,let form: ActionDataform }: {data: PageDatadata:import PageDataPageData,form: ActionDataform:import ActionDataActionData } =function $props(): any namespace $props$props();Declares the props that a component accepts. Example:
let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();Avec Svelte 4, vous utiliseriez plutôt
export let dataetexport let formpour déclarer les propriétés.
Erreurs de validation
Si la requête n’a pas pu être traitée à cause de données non valides, vous pouvez renvoyer des
erreurs de validation — en plus des valeurs précédemment soumises par le formulaire — à
l’utilisateur ou l’utilisatrice afin qu’il ou elle ré-essaye. La fonction fail vous permet de
renvoyer un statut HTTP (généralement 400 ou 422, dans le cas d’erreurs de validation) avec les
données. Le statut est rendu disponible via page.status et les données via form :
import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object. Call when form submission fails.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
if (!const email: FormDataEntryValue | nullemail) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)Create an ActionFailure object. Call when form submission fails.
email: string | nullemail, missing: booleanmissing: true });
}
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail);
if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)Create an ActionFailure object. Call when form submission fails.
email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
};import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object. Call when form submission fails.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
if (!const email: FormDataEntryValue | nullemail) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)Create an ActionFailure object. Call when form submission fails.
email: string | nullemail, missing: booleanmissing: true });
}
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail);
if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)Create an ActionFailure object. Call when form submission fails.
email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;Notez que par précaution, nous renvoyons uniquement l’email à la page — pas le mot de passe.
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">Le champ email est requis</p>{/if}
{#if form?.incorrect}<p class="error">Identifiants invalides !</p>{/if}
<label>
Email
<input name="email" type="email" value={form?.email ?? ''}>
</label>
<label>
Mot de passe
<input name="password" type="password">
</label>
<button>Se connecter</button>
<button formaction="?/register">S'inscrire</button>
</form>Les données renvoyées doivent être sérialisables en JSON. À part ça, la structure est complètement
libre. Par exemple, si vous avez plusieurs formulaires sur la même page, vous pourriez différencier
à quel élément <form> les données form font référence avec une propriété id ou similaire.
Redirections
Les redirections (et les erreurs) fonctionnent exactement de la même façon que dans
load :
import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object. Call when form submission fails.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
if (!const user: anyuser) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
}
if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
};import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object. Call when form submission fails.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object.
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe original request object.
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
if (!const user: anyuser) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
}
if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object. Call when form submission fails.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set: (name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on http://localhost, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect: redirect will keep the request method
308 Permanent Redirect: redirect will keep the request method, SEO will be transferred to the new page
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO inscrire l'utilisateur
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;Chargement de données
Après l’exécution d’une action, la page sera re-rendue (à moins qu’une redirection ou une erreur
inattendue se produise), avec la valeur de retour de l’action rendue disponible dans la page en tant
que propriété form. Ceci signifie que les fonctions load de la page vont être exécutées après
la fin de l’exécution d’une action.
Notez que handle est exécuté avant l’invocation de l’action, et n’est pas ré-exécutée avant les
fonctions load. Ceci signifie que si, par exemple, vous utiliser handle pour remplir
event.locals en fonction d’un cookie, vous devez mettre à jour event.locals lorsque vous
définissez ou supprimez le cookie lors d’une action :
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle(input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}): MaybePromise<...>
handle({ event: RequestEvent<Record<string, string>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) {
event: RequestEvent<Record<string, string>, string | null>event.RequestEvent<Record<string, string>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Record<string, string>, string | null>event.RequestEvent<Record<string, string>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Record<string, string>, string | null>event);
}import type { type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle } from '@sveltejs/kit';
export const const handle: Handlehandle: type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle = async ({ event: RequestEvent<Record<string, string>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) => {
event: RequestEvent<Record<string, string>, string | null>event.RequestEvent<Record<string, string>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Record<string, string>, string | null>event.RequestEvent<Record<string, string>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Record<string, string>, string | null>event);
};/** @type {import('./$types').PageServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user
};
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => {
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.delete: (name: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
delete('sessionid', { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends LayoutParams<"/"> = Record<string, string>, RouteId extends RouteId | null = string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
};import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) => {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user
};
};
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => {
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.delete: (name: string, opts: CookieSerializeOptions & {
path: string;
}) => void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
delete('sessionid', { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends LayoutParams<"/"> = Record<string, string>, RouteId extends RouteId | null = string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;Amélioration progressive
Dans les sections précédentes, nous avons construit une action /login qui fonctionne sans
JavaScript côté client — aucun fetch n’a
été impliqué. C’est super, mais lorsque JavaScript est disponible, nous pouvons améliorer
progressivement nos interactions de formulaire pour fournir une meilleure expérience utilisateur.
use:enhance
La manière la plus simple d’améliorer un formulaire progressivement est d’ajouter l’action
use:action :
<script>
import { enhance } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
</script>
<form method="POST" use:enhance><script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
</script>
<form method="POST" use:enhance>
use:enhancepeut uniquement être utilisé avec des formulaires qui ontmethod="POST"et pointent vers des actions définies dans un fichier+page.server.js. Cela ne fonctionnera pas avecmethod="GET", qui est le fonctionnement par défaut pour des formulaires sans méthode précisée. Utiliseruse:enhancesur des formulaires sansmethod="POST"ou cibler un endpoint+server.jsva provoquer une erreur.
Oui, il peut y avoir de la confusion entre l’action
enhanceet<form action>, qui sont toutes les deux appelées “action”. Cette documentation est pleine d’actions. Désolé.
Sans argument, use:enhance va émuler le comportement natif du navigateur, simplement sans le
rechargement complet de la page. Plus précisément, cela va :
- mettre à jour la propriété
form, ainsi quepage.formetpage.statuslors d’une réponse réussie ou invalide, mais uniquement si l’action est sur la même page que celle depuis laquelle vous soumettez. Par exemple, si vous formulaire ressemble à<form action="/somewhere/else" ..>, la propriétéformet l’étatpage.formne seront pas mis à jour. En effet, la soumission native d’un formulaire vous redirige vers la page sur laquelle l’action est définie. Si vous souhaitez tout de même les mettre à jour, vous devez utiliserapplyAction - réinitialiser l’élément
<form> - invalider toutes les données avec
invalidateAllsi la réponse est réussie - appeler
gotosi la réponse est une redirection - afficher la frontière d’erreur
+errorla plus proche si une erreur se produit - réinitialiser le focus sur l’élément approprié
Personnaliser use:enhance
Pour personnaliser le comportement, vous pouvez fournir une SubmitFunction qui sera exécutée
immédiatement avant la soumission du formulaire, et (optionnellement) renvoyer un callback qui
fournira une ActionResult.
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
// `formElement` est l'élément `<form>`
// `formData` est son objet `FormData` qui est sur le point d'être soumis
// `action` est l'URL vers laquelle le formulaire pointe
// exécuter `cancel()` va empêcher la soumission
// `submitter` est l'élément `HTMLElement` qui a provoqué la soumission du formulaire
return async ({ result, update }) => {
// `result` est un objet `ActionResult`
// `update` est une fonction qui déclenche la logique qui aurait été exécutée par défaut si ce
// callback n'avait pas été défini
};
}}
>Vous pouvez utiliser ces fonctions pour afficher et cacher des éléments d’interface de chargement, entre autres.
Si vous renvoyez un callback, vous écrasez le comportement post-soumission par défaut. Pour le
récupérer, appelez update, qui accepte les paramètres invalidateAll et reset, ou utilisez
applyAction sur le résultat :
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` est un objet `ActionResult`
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
><script lang="ts">
import { enhance, applyAction } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` est un objet `ActionResult`
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>Le comportement de applyAction(result) dépend de result.type :
success,failure— définitpage.statusen tant queresult.statuset met à jourformetpage.formen tant queresult.data(indépendamment de la source de soumission, à la différence duupdatevenant deenhance)redirect— exécutegoto(result.location, { invalidateAll: true })error— affiche la frontière+errorla plus proche en utilisantresult.error
Dans tous les cas, le focus sera réinitialisé.
Gestionnaire personnalisé d’évènement [VO]Custom event listener
Nous pouvons également implémenter de l’amélioration progressive nous-même, sans use:enhance, avec
un gestionnaire d’évènement normal sur l’élément <form> :
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {import('./$types').PageProps} */
let { form } = $props();
/** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget, event.submitter);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
if (result.type === 'success') {
// ré-exécute toutes les fonctions `load`, en cas de succès
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit={handleSubmit}>
<!-- contenu -->
</form><script lang="ts">
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
import type { PageProps } from './$types';
import type { ActionResult } from '@sveltejs/kit';
let { form }: PageProps = $props();
async function handleSubmit(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}) {
event.preventDefault();
const data = new FormData(event.currentTarget, event.submitter);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
const result: ActionResult = deserialize(await response.text());
if (result.type === 'success') {
// ré-exécute toutes les fonctions `load`, en cas de succès
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit={handleSubmit}>
<!-- contenu -->
</form>Notez que vous avez besoin de deserialize la réponse avant de la traiter en utilisant la méthode
correspondante importée depuis $app/forms. JSON.parse() ne suffit pas car les actions de
formulaires — comme les fonctions load — supportent également le renvoi d’objets Date ou
BigInt.
Si vous avez un fichier +server.js associé à votre +page.server.js, les requêtes fetch vont y
être dirigées par défaut. Si vous souhaitez envoyer une requête POST à une action de
+page.server.js, utilisez l’en-tête personnalisée x-sveltekit-action :
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(this.action, {
RequestInit.method?: string | undefinedA string to set request’s method.
method: 'POST',
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request’s body.
body: data,
RequestInit.headers?: HeadersInit | undefinedA Headers object, an object literal, or an array of two-item arrays to set request’s headers.
headers: {
'x-sveltekit-action': 'true'
}
});Alternatives
Les actions de formulaire sont la méthode recommandée pour envoyer des données au serveur,
puisqu’elles peuvent être améliorées progressivement, mais vous pouvez également utiliser des
fichiers +server.js pour exposer (par exemple) une API JSON. Voici ce à quoi une
telle interaction pourrait ressembler :
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Relancer la CI</button><script lang="ts">
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Relancer la CI</button>/** @type {import('./$types').RequestHandler} */
export function function POST(): voidPOST() {
// faire quelque chose
}import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export const const POST: RequestHandlerPOST: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = () => {
// faire quelque chose
};GET vs POST
Comme nous venons de le voir, pour invoquer une action de formulaire, vous devez utiliser
method="POST".
Certains formulaires n’ont pas besoin d’utiliser POST pour envoyer des données au serveur — les
inputs de recherche par exemple. Pour ces cas-là, vous pouvez utiliser method="GET" (ou de manière
équivalente, aucune method), et SvelteKit va traiter ces requêtes comme si elles venaient
d’éléments <a>, utilisant le routeur client plutôt qu’une navigation impliquant le rechargement
complet de la page :
<form action="/search">
<label>
Rechercher
<input name="q">
</label>
</form>Soumettre ce formulaire va déclencher une navigation vers /search?q=... et invoquer votre fonction
load, mais ne va invoquer aucune action. Comme pour les éléments <a>, vous pouvez définir les
attributs
data-sveltekit-reload,
data-sveltekit-replacestate,
data-sveltekit-keepfocus et
data-sveltekit-noscroll sur l’élément <form> pour
contrôler le comportement du routeur.
Sur le même sujet
Modifier cette page sur Github llms.txt