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, and as such 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
:
export default {
kit: {
experimental: {
remoteFunctions: boolean;
};
}
kit: {
experimental: {
remoteFunctions: boolean;
}
experimental: {
remoteFunctions: boolean
remoteFunctions: true
}
}
};
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.
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/database
and$lib/server/auth
. These are purely for illustrative purposes — you can use whatever database client and auth setup you like.The
db.sql
function 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>
{#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>
{#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
await
form.
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 = 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 = 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 v
v 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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => {
const [const post: any
post] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT * FROM post
WHERE slug = ${slug: string
slug}
`;
if (!const post: any
post) 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: any
post;
});
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 updated via its refresh
method:
<button onclick={() => getPosts().refresh()}>
Check for new posts
</button>
Queries are cached while they’re on the page, meaning
getPosts() === getPosts()
. As such, you don’t need a reference likeconst posts = getPosts()
in order to refresh the query.
form
The form
function makes it easy to write data to the server. It takes a callback that receives the current FormData
...
import * as import v
v 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): never
Redirect 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<T>(fn: (data: FormData) => MaybePromise<T>): RemoteForm<T>
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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => { /* ... */ });
export const const createPost: RemoteForm<never>
createPost = form<never>(fn: (data: FormData) => Promise<never>): RemoteForm<never>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// Check the user is logged in
const const user: auth.User | null
user = 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 | null
user) 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 title: FormDataEntryValue | null
title = data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('title');
const const content: FormDataEntryValue | null
content = data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('content');
// Check the data is valid
if (typeof const title: FormDataEntryValue | null
title !== 'string' || typeof const content: FormDataEntryValue | null
content !== 'string') {
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(400, 'Title and content are required');
}
const const slug: string
slug = const title: string
title.String.toLowerCase(): string
Converts 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: string
slug}, ${const title: string
title}, ${const content: string
content})
`;
// Redirect to the newly created page
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect 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: string
slug}`);
});
...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>
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 onsubmit
handler that progressively enhances the form when JavaScript is available, submitting data without reloading the entire page.
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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => { /* ... */ });
export const const createPost: RemoteForm<never>
createPost = form<never>(fn: (data: FormData) => Promise<never>): RemoteForm<never>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// form logic goes here...
// Refresh `getPosts()` on the server, and send
// the data back with the result of `createPost`
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): never
Redirect 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}`);
});
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<{
success: boolean;
}>(fn: (data: FormData) => MaybePromise<{
success: boolean;
}>): RemoteForm<{
success: boolean;
}>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// ...
return { success: boolean
success: 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
result
value 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');
}
})}>
<input name="title" />
<textarea name="content"></textarea>
<button>publish</button>
</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');
}
})}>
<input name="title" />
<textarea name="content"></textarea>
<button>publish</button>
</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[]): RemoteQueryOverride
Temporarily 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: Post
newPost, ...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 login 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
form
where possible, since it gracefully degrades if JavaScript is disabled or fails to load.
As with query
, if the function accepts an argument it should be validated by passing a Standard Schema as the first argument to command
.
import * as import v
v 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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
const [const row: any
row] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT likes
FROM item
WHERE id = ${id: string
id}
`;
return const row: any
row.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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: string
id}
`;
});
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.
Single-flight mutations
As with forms, any queries on the page (such as getLikes(item.id)
in the example above) will automatically be refreshed following a successful command. But we can make things more efficient by telling SvelteKit which queries will be affected by the command, either 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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => { /* ... */ });
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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: string
id}
`;
const getLikes: (arg: string) => RemoteQuery<void>
getLikes(id: string
id).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();
});
...or when we call it:
try {
await const addLike: (arg: string) => Promise<void> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<...>;
}
addLike(const item: Item
item.Item.id: string
id).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>
updates(const getLikes: (arg: string) => RemoteQuery<number>
getLikes(const item: Item
item.Item.id: string
id));
} catch (var error: unknown
error) {
function showToast(message: string): void
showToast('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: Item
item.Item.id: string
id).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>
updates(
const getLikes: (arg: string) => RemoteQuery<number>
getLikes(const item: Item
item.Item.id: string
id).function withOverride(update: (current: number) => number): RemoteQueryOverride
Temporarily 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: number
n) => n: number
n + 1)
);
} catch (var error: unknown
error) {
function showToast(message: string): void
showToast('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 v
v 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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => {
const [const post: any
post] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT * FROM post
WHERE slug = ${slug: string
slug}
`;
if (!const post: any
post) 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: any
post;
});
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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'first-post',
'second-post',
'third-post'
]
}
);
Svelte does not yet support asynchronous server-side rendering, and as such 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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
dynamic?: boolean | undefined
dynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'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 v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
dynamic?: boolean | undefined
dynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'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: any
event, issues: any
issues }) {
return {
message: string
message: '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: HandleValidationError
handleValidationError: 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<AppLayoutParams<"/">, any>
event, issues: StandardSchemaV1.Issue[]
issues }) => {
return {
App.Error.message: string
message: '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: string
id }: { id: string
id: string }) => {
// the shape might not actually be what TypeScript thinks
// since bad actors might call this function with other arguments
});
form
does not accept a schema since you are always passed aFormData
object. You are free to parse and validate this as you see fit.
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(): RequestEvent<findUser<"/">, any>
Returns 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 findUser
findUser } 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: any
user = await function getUser(): any
getUser();
return {
name: any
name: const user: any
user.name,
avatar: any
avatar: const user: any
user.avatar
};
});
// this function could be called from multiple places
function function getUser(): any
getUser() {
const { const cookies: Cookies
Get or set cookies related to the current request
cookies, const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals } = function getRequestEvent(): RequestEvent<findUser<"/">, any>
Returns 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();
const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.userPromise ??= import findUser
findUser(const cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('session_id'));
return await const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.userPromise;
}
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