Remote functions
Available since 2.27
Remote functions are a tool for type-safe communication between client and server. They can be called anywhere in your app, but always run on the server, meaning they can safely access server-only modules containing things like environment variables and database clients.
Combined with Svelte’s experimental support for await, it allows you to load and manipulate data directly inside your components.
This feature is currently experimental, meaning it is likely to contain bugs and is subject to change without notice. You must opt in by adding the kit.experimental.remoteFunctions option in your svelte.config.js and optionally, the compilerOptions.experimental.async option to use await in components:
/** @type {import('@sveltejs/kit').Config} */
const const config: {
kit: {
experimental: {
remoteFunctions: boolean;
};
};
compilerOptions: {
experimental: {
async: boolean;
};
};
}
config = {
kit: {
experimental: {
remoteFunctions: boolean;
};
}
kit: {
experimental: {
remoteFunctions: boolean;
}
experimental: {
remoteFunctions: booleanremoteFunctions: true
}
},
compilerOptions: {
experimental: {
async: boolean;
};
}
compilerOptions: {
experimental: {
async: boolean;
}
experimental: {
async: booleanasync: true
}
}
};
export default const config: {
kit: {
experimental: {
remoteFunctions: boolean;
};
};
compilerOptions: {
experimental: {
async: boolean;
};
};
}
config;Overview
Remote functions are exported from a .remote.js or .remote.ts file, and come in four flavours: query, form, command and prerender. On the client, the exported functions are transformed to fetch wrappers that invoke their counterparts on the server via a generated HTTP endpoint. Remote files must be placed in your src directory.
query
The query function allows you to read dynamic data from the server (for static data, consider using prerender instead):
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getPosts: RemoteQueryFunction<void, any[]>getPosts = query<any[]>(fn: () => MaybePromise<any[]>): RemoteQueryFunction<void, any[]> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(async () => {
const const posts: any[]posts = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return const posts: any[]posts;
});Throughout this page, you’ll see imports from fictional modules like
$lib/server/databaseand$lib/server/auth. These are purely for illustrative purposes — you can use whatever database client and auth setup you like.The
db.sqlfunction above is a tagged template function that escapes any interpolated values.
The query returned from getPosts works as a Promise that resolves to posts:
<script>
import { getPosts } from './data.remote';
</script>
<h1>Recent posts</h1>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul><script lang="ts">
import { getPosts } from './data.remote';
</script>
<h1>Recent posts</h1>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>Until the promise resolves — and if it errors — the nearest <svelte:boundary> will be invoked.
While using await is recommended, as an alternative the query also has loading, error and current properties:
<script>
import { getPosts } from './data.remote';
const query = getPosts();
</script>
<h1>Recent posts</h1>
{#if query.error}
<p>oops!</p>
{:else if query.loading}
<p>loading...</p>
{:else}
<ul>
{#each query.current as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
{/if}<script lang="ts">
import { getPosts } from './data.remote';
const query = getPosts();
</script>
<h1>Recent posts</h1>
{#if query.error}
<p>oops!</p>
{:else if query.loading}
<p>loading...</p>
{:else}
<ul>
{#each query.current as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
{/if}For the rest of this document, we’ll use the
awaitform.
Query arguments
Query functions can accept an argument, such as the slug of an individual post:
<script>
import { getPost } from '../data.remote';
let { params } = $props();
const post = $derived(await getPost(params.slug));
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div><script lang="ts">
import { getPost } from '../data.remote';
let { params } = $props();
const post = $derived(await getPost(params.slug));
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>Since getPost exposes an HTTP endpoint, it’s important to validate this argument to be sure that it’s the correct type. For this, we can use any Standard Schema validation library such as Zod or Valibot:
import * as import vv from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getPosts: RemoteQueryFunction<void, void>getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, any>getPost = query<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any): RemoteQueryFunction<string, any> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: stringslug) => {
const [const post: anypost] = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT * FROM post
WHERE slug = ${slug: stringslug}
`;
if (!const post: anypost) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
return const post: anypost;
});Both the argument and the return value are serialized with devalue, which handles types like Date and Map (and custom types defined in your transport hook) in addition to JSON.
Refreshing queries
Any query can be re-fetched via its refresh method, which retrieves the latest value from the server:
<button onclick={() => getPosts().refresh()}>
Check for new posts
</button>Queries are cached while they’re on the page, meaning
getPosts() === getPosts(). This means you don’t need a reference likeconst posts = getPosts()in order to update the query.
query.batch
query.batch works like query except that it batches requests that happen within the same macrotask. This solves the so-called n+1 problem: rather than each query resulting in a separate database call (for example), simultaneous queries are grouped together.
On the server, the callback receives an array of the arguments the function was called with. It must return a function of the form (input: Input, index: number) => Output. SvelteKit will then call this with each of the input arguments to resolve the individual calls with their results.
import * as import vv from 'valibot';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getWeather: RemoteQueryFunction<string, any>getWeather = function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query.function query.batch<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (args: string[]) => MaybePromise<(arg: string, idx: number) => any>): RemoteQueryFunction<string, any> (+1 overload)Creates a batch query function that collects multiple calls and executes them in a single request
See Remote functions for full documentation.
batch(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (cities: string[]cities) => {
const const weather: any[]weather = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT * FROM weather
WHERE city = ANY(${cities: string[]cities})
`;
const const lookup: Map<any, any>lookup = new var Map: MapConstructor
new <any, any>(iterable?: Iterable<readonly [any, any]> | null | undefined) => Map<any, any> (+3 overloads)
Map(const weather: any[]weather.Array<any>.map<[any, any]>(callbackfn: (value: any, index: number, array: any[]) => [any, any], thisArg?: any): [any, any][]Calls a defined callback function on each element of an array, and returns an array that contains the results.
map(w: anyw => [w: anyw.city, w: anyw]));
return (city: stringcity) => const lookup: Map<any, any>lookup.Map<any, any>.get(key: any): anyReturns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
get(city: stringcity);
});<script>
import CityWeather from './CityWeather.svelte';
import { getWeather } from './weather.remote.js';
let { cities } = $props();
let limit = $state(5);
</script>
<h2>Weather</h2>
{#each cities.slice(0, limit) as city}
<h3>{city.name}</h3>
<CityWeather weather={await getWeather(city.id)} />
{/each}
{#if cities.length > limit}
<button onclick={() => limit += 5}>
Load more
</button>
{/if}<script lang="ts">
import CityWeather from './CityWeather.svelte';
import { getWeather } from './weather.remote.js';
let { cities } = $props();
let limit = $state(5);
</script>
<h2>Weather</h2>
{#each cities.slice(0, limit) as city}
<h3>{city.name}</h3>
<CityWeather weather={await getWeather(city.id)} />
{/each}
{#if cities.length > limit}
<button onclick={() => limit += 5}>
Load more
</button>
{/if}form
The form function makes it easy to write data to the server. It takes a callback that receives data constructed from the submitted FormData...
import * as import vv from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error, 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 { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query, function form<Output>(fn: () => Output): RemoteForm<void, Output> (+2 overloads)Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
import * as module "$lib/server/auth"auth from '$lib/server/auth';
export const const getPosts: RemoteQueryFunction<void, void>getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, void>getPost = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: stringslug) => { /* ... */ });
export const const createPost: RemoteForm<{
title: string;
content: string;
}, never>
createPost = form<v.ObjectSchema<{
readonly title: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
readonly content: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
}, undefined>, never>(validate: v.ObjectSchema<...>, fn: (data: {
...;
}) => Promise<...>): RemoteForm<...> (+2 overloads)
Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form(
import vv.object<{
readonly title: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
readonly content: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
}>(entries: {
readonly title: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
readonly content: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>;
}): v.ObjectSchema<...> (+1 overload)
export object
Creates an object schema.
Hint: This schema removes unknown entries. The output will only include the
entries you specify. To include unknown entries, use looseObject. To
return an issue for unknown entries, use strictObject. To include and
validate unknown entries, use objectWithRest.
object({
title: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>title: import vv.pipe<v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>>(schema: v.StringSchema<undefined>, item1: v.NonEmptyAction<string, undefined> | v.PipeAction<string, string, v.NonEmptyIssue<...>>): v.SchemaWithPipe<...> (+20 overloads)
export pipe
Adds a pipeline to a schema, that can validate and transform its input.
pipe(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), import vv.nonEmpty<string>(): v.NonEmptyAction<string, undefined> (+1 overload)
export nonEmpty
Creates a non-empty validation action.
nonEmpty()),
content: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>]>content:import vv.pipe<v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>>(schema: v.StringSchema<undefined>, item1: v.NonEmptyAction<string, undefined> | v.PipeAction<string, string, v.NonEmptyIssue<...>>): v.SchemaWithPipe<...> (+20 overloads)
export pipe
Adds a pipeline to a schema, that can validate and transform its input.
pipe(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), import vv.nonEmpty<string>(): v.NonEmptyAction<string, undefined> (+1 overload)
export nonEmpty
Creates a non-empty validation action.
nonEmpty())
}),
async ({ title: stringtitle, content: stringcontent }) => {
// Check the user is logged in
const const user: auth.User | nulluser = await module "$lib/server/auth"auth.function getUser(): Promise<auth.User | null>Gets a user’s info from their cookies, using getRequestEvent
getUser();
if (!const user: auth.User | nulluser) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(401, 'Unauthorized');
const const slug: stringslug = title: stringtitle.String.toLowerCase(): stringConverts all the alphabetic characters in a string to lowercase.
toLowerCase().String.replace(searchValue: {
[Symbol.replace](string: string, replaceValue: string): string;
}, replaceValue: string): string (+3 overloads)
Passes a string and
{@linkcode
replaceValue
}
to the [Symbol.replace] method on
{@linkcode
searchValue
}
. This method is expected to implement its own replacement algorithm.
replace(/ /g, '-');
// Insert into the database
await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
INSERT INTO post (slug, title, content)
VALUES (${const slug: stringslug}, ${title: stringtitle}, ${content: stringcontent})
`;
// Redirect to the newly created page
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, `/blog/${const slug: stringslug}`);
}
);...and returns an object that can be spread onto a <form> element. The callback is called whenever the form is submitted.
<script>
import { createPost } from '../data.remote';
</script>
<h1>Create a new post</h1>
<form {...createPost}>
<label>
<h2>Title</h2>
<input name="title" />
</label>
<label>
<h2>Write your post</h2>
<textarea name="content"></textarea>
</label>
<button>Publish!</button>
</form><script lang="ts">
import { createPost } from '../data.remote';
</script>
<h1>Create a new post</h1>
<form {...createPost}>
<label>
<h2>Title</h2>
<input name="title" />
</label>
<label>
<h2>Write your post</h2>
<textarea name="content"></textarea>
</label>
<button>Publish!</button>
</form>As with query, if the callback uses the submitted data, it should be validated by passing a Standard Schema as the first argument to form. The one difference is to query is that the schema inputs must all be of type string or File, since that’s all the original FormData provides. You can however coerce the value into a different type — how to do that depends on the validation library you use.
import * as import vv from 'valibot';
import { function form<Output>(fn: () => Output): RemoteForm<void, Output> (+2 overloads)Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form } from '$app/server';
export const const setCount: RemoteForm<{
count: string;
}, void>
setCount = form<v.ObjectSchema<{
readonly count: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, number>, v.NumberSchema<undefined>]>;
}, undefined>, void>(validate: v.ObjectSchema<...>, fn: (data: {
...;
}) => MaybePromise<...>): RemoteForm<...> (+2 overloads)
Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form(
import vv.object<{
readonly count: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, number>, v.NumberSchema<undefined>]>;
}>(entries: {
readonly count: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, number>, v.NumberSchema<undefined>]>;
}): v.ObjectSchema<...> (+1 overload)
export object
Creates an object schema.
Hint: This schema removes unknown entries. The output will only include the
entries you specify. To include unknown entries, use looseObject. To
return an issue for unknown entries, use strictObject. To include and
validate unknown entries, use objectWithRest.
object({
// Valibot:
count: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TransformAction<string, number>, v.NumberSchema<undefined>]>count: import vv.pipe<v.StringSchema<undefined>, v.TransformAction<string, number>, v.NumberSchema<undefined>>(schema: v.StringSchema<undefined>, item1: v.TransformAction<string, number> | v.PipeAction<...>, item2: v.NumberSchema<...> | v.PipeAction<...>): v.SchemaWithPipe<...> (+20 overloads)
export pipe
Adds a pipeline to a schema, that can validate and transform its input.
pipe(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), import vv.transform<string, number>(operation: (input: string) => number): v.TransformAction<string, number>
export transform
Creates a custom transformation action.
transform((s: strings) => var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(s: strings)), import vv.function number(): v.NumberSchema<undefined> (+1 overload)
export number
Creates a number schema.
number()),
// Zod:
// count: z.coerce.number<string>()
}),
async ({ count: numbercount }) => {
// ...
}
);The name attributes on the form controls must correspond to the properties of the schema — title and content in this case. If you schema contains objects, use object notation:
<!--
results in a
{
name: { first: string, last: string },
jobs: Array<{ title: string, company: string }>
}
object
-->
<input name="name.first" />
<input name="name.last" />
{#each jobs as job, idx}
<input name="jobs[{idx}].title">
<input name="jobs[{idx}].company">
{/each}To indicate a repeated field, use a [] suffix:
<label><input type="checkbox" name="language[]" value="html" /> HTML</label>
<label><input type="checkbox" name="language[]" value="css" /> CSS</label>
<label><input type="checkbox" name="language[]" value="js" /> JS</label>If you’d like type safety and autocomplete when setting name attributes, use the form object’s field method:
<label>
<h2>Title</h2>
<input name={createPost.field('title')} />
</label>This will error during typechecking if title does not exist on your schema.
The form object contains method and action properties that allow it to work without JavaScript (i.e. it submits data and reloads the page). It also has an attachment that progressively enhances the form when JavaScript is available, submitting data without reloading the entire page.
Validation
If the submitted data doesn’t pass the schema, the callback will not run. Instead, the form object’s issues object will be populated:
<form {...createPost}>
<label>
<h2>Title</h2>
{#if createPost.issues.title}
{#each createPost.issues.title as issue}
<p class="issue">{issue.message}</p>
{/each}
{/if}
<input
name="title"
aria-invalid={!!createPost.issues.title}
/>
</label>
<label>
<h2>Write your post</h2>
{#if createPost.issues.content}
{#each createPost.issues.content as issue}
<p class="issue">{issue.message}</p>
{/each}
{/if}
<textarea
name="content"
aria-invalid={!!createPost.issues.content}
></textarea>
</label>
<button>Publish!</button>
</form>You don’t need to wait until the form is submitted to validate the data — you can call validate() programmatically, for example in an oninput callback (which will validate the data on every keystroke) or an onchange callback:
<form {...createPost} oninput={() => createPost.validate()}>
<!-- -->
</form>By default, issues will be ignored if they belong to form controls that haven’t yet been interacted with. To validate all inputs, call validate({ includeUntouched: true }).
For client-side validation, you can specify a preflight schema which will populate issues and prevent data being sent to the server if the data doesn’t validate:
<script>
import * as v from 'valibot';
import { createPost } from '../data.remote';
const schema = v.object({
title: v.pipe(v.string(), v.nonEmpty()),
content: v.pipe(v.string(), v.nonEmpty())
});
</script>
<h1>Create a new post</h1>
<form {...createPost.preflight(schema)}>
<!-- -->
</form>The preflight schema can be the same object as your server-side schema, if appropriate, though it won’t be able to do server-side checks like ‘this value already exists in the database’. Note that you cannot export a schema from a
.remote.tsor.remote.jsfile, so the schema must either be exported from a shared module, or from a<script module>block in the component containing the<form>.
Live inputs
The form object contains a input property which reflects its current value. As the user interacts with the form, input is automatically updated:
<form {...createPost}>
<!-- -->
</form>
<div class="preview">
<h2>{createPost.input.title}</h2>
<div>{@html render(createPost.input.content)}</div>
</div>Handling sensitive data
In the case of a non-progressively-enhanced form submission (i.e. where JavaScript is unavailable, for whatever reason) input is also populated if the submitted data is invalid, so that the user does not need to fill the entire form out from scratch.
You can prevent sensitive data (such as passwords and credit card numbers) from being sent back to the user by using a name with a leading underscore:
<form {...register}>
<label>
Username
<input
name="username"
value={register.input.username}
aria-invalid={!!register.issues.username}
/>
</label>
<label>
Password
<input
type="password"
name="_password"
aria-invalid={!!register.issues._password}
/>
</label>
<button>Sign up!</button>
</form>In this example, if the data does not validate, only the first <input> will be populated when the page reloads.
Single-flight mutations
By default, all queries used on the page (along with any load functions) are automatically refreshed following a successful form submission. This ensures that everything is up-to-date, but it’s also inefficient: many queries will be unchanged, and it requires a second trip to the server to get the updated data.
Instead, we can specify which queries should be refreshed in response to a particular form submission. This is called a single-flight mutation, and there are two ways to achieve it. The first is to refresh the query on the server, inside the form handler:
export const const getPosts: RemoteQueryFunction<void, void>getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, void>getPost = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: stringslug) => { /* ... */ });
export const const createPost: RemoteForm<{}, never>createPost = form<v.ObjectSchema<{}, undefined>, never>(validate: v.ObjectSchema<{}, undefined>, fn: (data: {}) => Promise<never>): RemoteForm<{}, never> (+2 overloads)Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form(
import vv.object<{}>(entries: {}): v.ObjectSchema<{}, undefined> (+1 overload)
export object
Creates an object schema.
Hint: This schema removes unknown entries. The output will only include the
entries you specify. To include unknown entries, use looseObject. To
return an issue for unknown entries, use strictObject. To include and
validate unknown entries, use objectWithRest.
object({/* ... */}),
async (data: {}data) => {
// form logic goes here...
// Refresh `getPosts()` on the server, and send
// the data back with the result of `createPost`
await const getPosts: (arg: void) => RemoteQuery<void>getPosts().function refresh(): Promise<void>On the client, this function will re-fetch the query from the server.
On the server, this can be called in the context of a command or form and the refreshed data will accompany the action response back to the client.
This prevents SvelteKit needing to refresh all queries on the page in a second server round-trip.
refresh();
// Redirect to the newly created page
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, `/blog/${const slug: ""slug}`);
}
);
export const const updatePost: RemoteForm<{}, void>updatePost = form<v.ObjectSchema<{}, undefined>, void>(validate: v.ObjectSchema<{}, undefined>, fn: (data: {}) => MaybePromise<void>): RemoteForm<{}, void> (+2 overloads)Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form(
import vv.object<{}>(entries: {}): v.ObjectSchema<{}, undefined> (+1 overload)
export object
Creates an object schema.
Hint: This schema removes unknown entries. The output will only include the
entries you specify. To include unknown entries, use looseObject. To
return an issue for unknown entries, use strictObject. To include and
validate unknown entries, use objectWithRest.
object({/* ... */}),
async (data: {}data) => {
// form logic goes here...
const const result: anyresult = const externalApi: anyexternalApi.update(const post: {
id: string;
}
post);
// The API already gives us the updated post,
// no need to refresh it, we can set it directly
await const getPost: (arg: string) => RemoteQuery<void>getPost(const post: {
id: string;
}post.id: stringid).function set(value: void): voidOn the client, this function will update the value of the query without re-fetching it.
On the server, this can be called in the context of a command or form and the specified data will accompany the action response back to the client.
const result: anyresult);
}
);The second is to drive the single-flight mutation from the client, which we’ll see in the section on enhance.
Returns and redirects
The example above uses redirect(...), which sends the user to the newly created page. Alternatively, the callback could return data, in which case it would be available as createPost.result:
export const const createPost: RemoteForm<{}, {
success: boolean;
}>
createPost = form<v.ObjectSchema<{}, undefined>, {
success: boolean;
}>(validate: v.ObjectSchema<{}, undefined>, fn: (data: {}) => MaybePromise<{
success: boolean;
}>): RemoteForm<{}, {
success: boolean;
}> (+2 overloads)
Creates a form object that can be spread onto a <form> element.
See Remote functions for full documentation.
form(
import vv.object<{}>(entries: {}): v.ObjectSchema<{}, undefined> (+1 overload)
export object
Creates an object schema.
Hint: This schema removes unknown entries. The output will only include the
entries you specify. To include unknown entries, use looseObject. To
return an issue for unknown entries, use strictObject. To include and
validate unknown entries, use objectWithRest.
object({/* ... */}),
async (data: {}data) => {
// ...
return { success: booleansuccess: true };
}
);<script>
import { createPost } from '../data.remote';
</script>
<h1>Create a new post</h1>
<form {...createPost}>
<!-- -->
</form>
{#if createPost.result?.success}
<p>Successfully published!</p>
{/if}<script lang="ts">
import { createPost } from '../data.remote';
</script>
<h1>Create a new post</h1>
<form {...createPost}>
<!-- -->
</form>
{#if createPost.result?.success}
<p>Successfully published!</p>
{/if}This value is ephemeral — it will vanish if you resubmit, navigate away, or reload the page.
The
resultvalue need not indicate success — it can also contain validation errors, along with any data that should repopulate the form on page reload.
If an error occurs during submission, the nearest +error.svelte page will be rendered.
enhance
We can customize what happens when the form is submitted with the enhance method:
<script>
import { createPost } from '../data.remote';
import { showToast } from '$lib/toast';
</script>
<h1>Create a new post</h1>
<form {...createPost.enhance(async ({ form, data, submit }) => {
try {
await submit();
form.reset();
showToast('Successfully published!');
} catch (error) {
showToast('Oh no! Something went wrong');
}
})}>
<!-- -->
</form><script lang="ts">
import { createPost } from '../data.remote';
import { showToast } from '$lib/toast';
</script>
<h1>Create a new post</h1>
<form {...createPost.enhance(async ({ form, data, submit }) => {
try {
await submit();
form.reset();
showToast('Successfully published!');
} catch (error) {
showToast('Oh no! Something went wrong');
}
})}>
<!-- -->
</form>The callback receives the form element, the data it contains, and a submit function.
To enable client-driven single-flight mutations, use submit().updates(...). For example, if the getPosts() query was used on this page, we could refresh it like so:
await function submit(): Promise<any> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>;
}
submit().function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>updates(function getPosts(): RemoteQuery<Post[]>getPosts());We can also override the current data while the submission is ongoing:
await function submit(): Promise<any> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>;
}
submit().function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>updates(
function getPosts(): RemoteQuery<Post[]>getPosts().function withOverride(update: (current: Post[]) => Post[]): RemoteQueryOverrideTemporarily override the value of a query. This is used with the updates method of a command or enhanced form submission to provide optimistic updates.
<script>
import { getTodos, addTodo } from './todos.remote.js';
const todos = getTodos();
</script>
<form {...addTodo.enhance(async ({ data, submit }) => {
await submit().updates(
todos.withOverride((todos) => [...todos, { text: data.get('text') }])
);
})}>
<input type="text" name="text" />
<button type="submit">Add Todo</button>
</form>
withOverride((posts: Post[]posts) => [const newPost: PostnewPost, ...posts: Post[]posts])
);The override will be applied immediately, and released when the submission completes (or fails).
buttonProps
By default, submitting a form will send a request to the URL indicated by the <form> element’s action attribute, which in the case of a remote function is a property on the form object generated by SvelteKit.
It’s possible for a <button> inside the <form> to send the request to a different URL, using the formaction attribute. For example, you might have a single form that allows you to log in or register depending on which button was clicked.
This attribute exists on the buttonProps property of a form object:
<script>
import { login, register } from '$lib/auth';
</script>
<form {...login}>
<label>
Your username
<input name="username" />
</label>
<label>
Your password
<input name="password" type="password" />
</label>
<button>login</button>
<button {...register.buttonProps}>register</button>
</form><script lang="ts">
import { login, register } from '$lib/auth';
</script>
<form {...login}>
<label>
Your username
<input name="username" />
</label>
<label>
Your password
<input name="password" type="password" />
</label>
<button>login</button>
<button {...register.buttonProps}>register</button>
</form>Like the form object itself, buttonProps has an enhance method for customizing submission behaviour.
command
The command function, like form, allows you to write data to the server. Unlike form, it’s not specific to an element and can be called from anywhere.
Prefer
formwhere possible, since it gracefully degrades if JavaScript is disabled or fails to load.
As with query and form, if the function accepts an argument, it should be validated by passing a Standard Schema as the first argument to command.
import * as import vv from 'valibot';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query, function command<Output>(fn: () => Output): RemoteCommand<void, Output> (+2 overloads)Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
command } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getLikes: RemoteQueryFunction<string, any>getLikes = query<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any): RemoteQueryFunction<string, any> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: stringid) => {
const [const row: anyrow] = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT likes
FROM item
WHERE id = ${id: stringid}
`;
return const row: anyrow.likes;
});
export const const addLike: RemoteCommand<string, Promise<void>>addLike = command<v.StringSchema<undefined>, Promise<void>>(validate: v.StringSchema<undefined>, fn: (arg: string) => Promise<void>): RemoteCommand<string, Promise<void>> (+2 overloads)Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
command(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: stringid) => {
await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: stringid}
`;
});Now simply call addLike, from (for example) an event handler:
<script>
import { getLikes, addLike } from './likes.remote';
import { showToast } from '$lib/toast';
let { item } = $props();
</script>
<button
onclick={async () => {
try {
await addLike(item.id);
} catch (error) {
showToast('Something went wrong!');
}
}}
>
add like
</button>
<p>likes: {await getLikes(item.id)}</p><script lang="ts">
import { getLikes, addLike } from './likes.remote';
import { showToast } from '$lib/toast';
let { item } = $props();
</script>
<button
onclick={async () => {
try {
await addLike(item.id);
} catch (error) {
showToast('Something went wrong!');
}
}}
>
add like
</button>
<p>likes: {await getLikes(item.id)}</p>Commands cannot be called during render.
Updating queries
To update getLikes(item.id), or any other query, we need to tell SvelteKit which queries need to be refreshed (unlike form, which by default invalidates everything, to approximate the behaviour of a native form submission).
We either do that inside the command itself...
export const const getLikes: RemoteQueryFunction<string, void>getLikes = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: stringid) => { /* ... */ });
export const const addLike: RemoteCommand<string, Promise<void>>addLike = command<v.StringSchema<undefined>, Promise<void>>(validate: v.StringSchema<undefined>, fn: (arg: string) => Promise<void>): RemoteCommand<string, Promise<void>> (+2 overloads)Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
command(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: stringid) => {
await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: stringid}
`;
const getLikes: (arg: string) => RemoteQuery<void>getLikes(id: stringid).function refresh(): Promise<void>On the client, this function will re-fetch the query from the server.
On the server, this can be called in the context of a command or form and the refreshed data will accompany the action response back to the client.
This prevents SvelteKit needing to refresh all queries on the page in a second server round-trip.
refresh();
// Just like within form functions you can also do
// getLikes(id).set(...)
// in case you have the result already
});...or when we call it:
try {
await const addLike: (arg: string) => Promise<void> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<...>;
}
addLike(const item: Itemitem.Item.id: stringid).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>updates(const getLikes: (arg: string) => RemoteQuery<number>getLikes(const item: Itemitem.Item.id: stringid));
} catch (var error: unknownerror) {
function showToast(message: string): voidshowToast('Something went wrong!');
}As before, we can use withOverride for optimistic updates:
try {
await const addLike: (arg: string) => Promise<void> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<...>;
}
addLike(const item: Itemitem.Item.id: stringid).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>updates(
const getLikes: (arg: string) => RemoteQuery<number>getLikes(const item: Itemitem.Item.id: stringid).function withOverride(update: (current: number) => number): RemoteQueryOverrideTemporarily override the value of a query. This is used with the updates method of a command or enhanced form submission to provide optimistic updates.
<script>
import { getTodos, addTodo } from './todos.remote.js';
const todos = getTodos();
</script>
<form {...addTodo.enhance(async ({ data, submit }) => {
await submit().updates(
todos.withOverride((todos) => [...todos, { text: data.get('text') }])
);
})}>
<input type="text" name="text" />
<button type="submit">Add Todo</button>
</form>
withOverride((n: numbern) => n: numbern + 1)
);
} catch (var error: unknownerror) {
function showToast(message: string): voidshowToast('Something went wrong!');
}prerender
The prerender function is similar to query, except that it will be invoked at build time to prerender the result. Use this for data that changes at most once per redeployment.
import { function prerender<Output>(fn: () => MaybePromise<Output>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<void, Output> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getPosts: RemotePrerenderFunction<void, any[]>getPosts = prerender<any[]>(fn: () => MaybePromise<any[]>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(async () => {
const const posts: any[]posts = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return const posts: any[]posts;
});You can use prerender functions on pages that are otherwise dynamic, allowing for partial prerendering of your data. This results in very fast navigation, since prerendered data can live on a CDN along with your other static assets.
In the browser, prerendered data is saved using the Cache API. This cache survives page reloads, and will be cleared when the user first visits a new deployment of your app.
When the entire page has
export const prerender = true, you cannot use queries, as they are dynamic.
Prerender arguments
As with queries, prerender functions can accept an argument, which should be validated with a Standard Schema:
import * as import vv from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import { function prerender<Output>(fn: () => MaybePromise<Output>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<void, Output> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender } from '$app/server';
import * as module "$lib/server/database"db from '$lib/server/database';
export const const getPosts: RemotePrerenderFunction<void, void>getPosts = prerender<void>(fn: () => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(async () => { /* ... */ });
export const const getPost: RemotePrerenderFunction<string, any>getPost = prerender<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: stringslug) => {
const [const post: anypost] = await module "$lib/server/database"db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>sql`
SELECT * FROM post
WHERE slug = ${slug: stringslug}
`;
if (!const post: anypost) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
return const post: anypost;
});Any calls to getPost(...) found by SvelteKit’s crawler while prerendering pages will be saved automatically, but you can also specify which values it should be called with using the inputs option:
export const const getPost: RemotePrerenderFunction<string, void>getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(
import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: stringslug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefinedinputs: () => [
'first-post',
'second-post',
'third-post'
]
}
);export const const getPost: RemotePrerenderFunction<string, void>getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(
import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: stringslug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefinedinputs: () => [
'first-post',
'second-post',
'third-post'
]
}
);Svelte does not yet support asynchronous server-side rendering, so it’s likely that you’re only calling remote functions from the browser, rather than during prerendering. Because of this, you will need to use
inputs, for now. We’re actively working on this roadblock.
By default, prerender functions are excluded from your server bundle, which means that you cannot call them with any arguments that were not prerendered. You can set dynamic: true to change this behaviour:
export const const getPost: RemotePrerenderFunction<string, void>getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(
import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: stringslug) => { /* ... */ },
{
dynamic?: boolean | undefineddynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefinedinputs: () => [
'first-post',
'second-post',
'third-post'
]
}
);export const const getPost: RemotePrerenderFunction<string, void>getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
prerender(
import vv.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: stringslug) => { /* ... */ },
{
dynamic?: boolean | undefineddynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefinedinputs: () => [
'first-post',
'second-post',
'third-post'
]
}
);Handling validation errors
As long as you’re not passing invalid data to your remote functions, there are only two reasons why the argument passed to a command, query or prerender function would fail validation:
- the function signature changed between deployments, and some users are currently on an older version of your app
- someone is trying to attack your site by poking your exposed endpoints with bad data
In the second case, we don’t want to give the attacker any help, so SvelteKit will generate a generic 400 Bad Request response. You can control the message by implementing the handleValidationError server hook, which, like handleError, must return an App.Error (which defaults to { message: string }):
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function function handleValidationError({ event, issues }: {
event: any;
issues: any;
}): {
message: string;
}
handleValidationError({ event: anyevent, issues: anyissues }) {
return {
message: stringmessage: 'Nice try, hacker!'
};
}import type { type HandleValidationError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: {
issues: Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>
The handleValidationError hook runs when the argument to a remote function fails validation.
It will be called with the validation issues and the event, and must return an object shape that matches App.Error.
HandleValidationError } from '@sveltejs/kit';
export const const handleValidationError: HandleValidationErrorhandleValidationError: type HandleValidationError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: {
issues: Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>
The handleValidationError hook runs when the argument to a remote function fails validation.
It will be called with the validation issues and the event, and must return an object shape that matches App.Error.
HandleValidationError = ({ event: RequestEvent<Record<string, string>, string | null>event, issues: StandardSchemaV1.Issue[]issues }) => {
return {
App.Error.message: stringmessage: 'Nice try, hacker!'
};
};If you know what you’re doing and want to opt out of validation, you can pass the string 'unchecked' in place of a schema:
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query } from '$app/server';
export const const getStuff: RemoteQueryFunction<{
id: string;
}, void>
getStuff = query<{
id: string;
}, void>(validate: "unchecked", fn: (arg: {
id: string;
}) => MaybePromise<void>): RemoteQueryFunction<{
id: string;
}, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query('unchecked', async ({ id: stringid }: { id: stringid: string }) => {
// the shape might not actually be what TypeScript thinks
// since bad actors might call this function with other arguments
});Using getRequestEvent
Inside query, form and command you can use getRequestEvent to get the current RequestEvent object. This makes it easy to build abstractions for interacting with cookies, for example:
import { function getRequestEvent(): RequestEventReturns the current RequestEvent. Can be used inside server hooks, server load functions, actions, and endpoints (and functions called by them).
In environments without AsyncLocalStorage, this must be called synchronously (i.e. not after an await).
getRequestEvent, function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query } from '$app/server';
import { import findUserfindUser } from '$lib/server/database';
export const const getProfile: RemoteQueryFunction<void, {
name: any;
avatar: any;
}>
getProfile = query<{
name: any;
avatar: any;
}>(fn: () => MaybePromise<{
name: any;
avatar: any;
}>): RemoteQueryFunction<void, {
name: any;
avatar: any;
}> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(async () => {
const const user: anyuser = await const getUser: (arg: void) => RemoteQuery<any>getUser();
return {
name: anyname: const user: anyuser.name,
avatar: anyavatar: const user: anyuser.avatar
};
});
// this query could be called from multiple places, but
// the function will only run once per request
const const getUser: RemoteQueryFunction<void, any>getUser = query<any>(fn: () => any): RemoteQueryFunction<void, any> (+2 overloads)Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch call.
See Remote functions for full documentation.
query(() => {
const { const cookies: CookiesGet or set cookies related to the current request
cookies } = function getRequestEvent(): RequestEventReturns the current RequestEvent. Can be used inside server hooks, server load functions, actions, and endpoints (and functions called by them).
In environments without AsyncLocalStorage, this must be called synchronously (i.e. not after an await).
getRequestEvent();
return await import findUserfindUser(const 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('session_id'));
});Note that some properties of RequestEvent are different inside remote functions. There are no params or route.id, and you cannot set headers (other than writing cookies, and then only inside form and command functions), and url.pathname is always / (since the path that’s actually being requested by the client is purely an implementation detail).
Redirects
Inside query, form and prerender functions it is possible to use the redirect(...) function. It is not possible inside command functions, as you should avoid redirecting here. (If you absolutely have to, you can return a { redirect: location } object and deal with it in the client.)
Edit this page on GitHub llms.txt