Skip to main content

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:

svelte.config
export default {
	
kit: {
    experimental: {
        remoteFunctions: boolean;
    };
}
kit
: {
experimental: {
    remoteFunctions: boolean;
}
experimental
: {
remoteFunctions: booleanremoteFunctions: 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):

src/routes/blog/data.remote
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:

src/routes/blog/+page
<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:

src/routes/blog/+page
<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:

src/routes/blog/[slug]/+page
<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:

src/routes/blog/data.remote
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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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.

@returnsA 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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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 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 like const 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...

src/routes/blog/data.remote
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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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

See all redirect status codes

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
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 &#x3C;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.

@returnsA string schema.
string
(), async (slug: stringslug) => { /* ... */ });
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 &#x3C;form> element.

See Remote functions for full documentation.

form
(async (data: FormDatadata) => {
// 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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
error
(401, 'Unauthorized');
const const title: FormDataEntryValue | nulltitle = data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('title'); const const content: FormDataEntryValue | nullcontent = data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('content'); // Check the data is valid if (typeof const title: FormDataEntryValue | nulltitle !== 'string' || typeof const content: FormDataEntryValue | nullcontent !== '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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
error
(400, 'Title and content are required');
} const const slug: stringslug = const title: stringtitle.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.

@paramsearchValue An object that supports searching for and replacing matches within a string.
@paramreplaceValue The replacement text.
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}, ${const title: stringtitle}, ${const content: stringcontent}) `; // 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

See all redirect status codes

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
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.

src/routes/blog/new/+page
<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 vv.
function string(): v.StringSchema<undefined> (+1 overload)
export string

Creates a string schema.

@returnsA string schema.
string
(), async (slug: stringslug) => { /* ... */ });
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 &#x3C;form> element.

See Remote functions for full documentation.

form
(async (data: FormDatadata) => {
// 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

See all redirect status codes

@paramstatus The HTTP status code. Must be in the range 300-308.
@paramlocation The location to redirect to.
@throwsRedirect This error instructs SvelteKit to redirect to the specified location.
@throwsError If the provided status is invalid.
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:

src/routes/blog/data.remote
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 &#x3C;form> element.

See Remote functions for full documentation.

form
(async (data: FormDatadata) => {
// ... return { success: booleansuccess: true }; });
src/routes/blog/new/+page
<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:

src/routes/blog/new/+page
<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.

&#x3C;script>
  import { getTodos, addTodo } from './todos.remote.js';
  const todos = getTodos();
&#x3C;/script>

&#x3C;form {...addTodo.enhance(async ({ data, submit }) => {
  await submit().updates(
	todos.withOverride((todos) => [...todos, { text: data.get('text') }])
  );
}}>
  &#x3C;input type="text" name="text" />
  &#x3C;button type="submit">Add Todo&#x3C;/button>
&#x3C;/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 login or register depending on which button was clicked.

This attribute exists on the buttonProps property of a form object:

src/routes/login/+page
<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.

likes.remote
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.

@returnsA 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.

@returnsA 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:

+page
<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...

likes.remote
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.

@returnsA 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.

@returnsA 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
();
});

...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): 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.

&#x3C;script>
  import { getTodos, addTodo } from './todos.remote.js';
  const todos = getTodos();
&#x3C;/script>

&#x3C;form {...addTodo.enhance(async ({ data, submit }) => {
  await submit().updates(
	todos.withOverride((todos) => [...todos, { text: data.get('text') }])
  );
}}>
  &#x3C;input type="text" name="text" />
  &#x3C;button type="submit">Add Todo&#x3C;/button>
&#x3C;/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.

src/routes/blog/data.remote
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:

src/routes/blog/data.remote
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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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.

@returnsA 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.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
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:

src/routes/blog/data.remote

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.

@returnsA 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.

@returnsA 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, 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:

src/routes/blog/data.remote

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.

@returnsA 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.

@returnsA 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 }):

src/hooks.server
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function 
function handleValidationError({ event, issues }: {
    event: any;
    issues: any;
}): {
    message: string;
}
@type{import('@sveltejs/kit').HandleValidationError}
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<AppLayoutParams<"/">, any>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:

data.remote
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 });

form does not accept a schema since you are always passed a FormData 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:

user.remote
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).

@since2.20.0
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 function getUser(): anygetUser(); return { name: anyname: const user: anyuser.name, avatar: anyavatar: const user: anyuser.avatar }; }); // this function could be called from multiple places function function getUser(): anygetUser() { 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).

@since2.20.0
getRequestEvent
();
const locals: App.Locals

Contains custom data that was added to the request within the server handle hook.

locals
.userPromise ??= import findUserfindUser(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.

@paramname the name of the cookie
@paramopts the options, passed directly to cookie.parse. See documentation here
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