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](server-only-modules) containing things like environment variables and database clients.
Combined with Svelte's experimental support for [`await`](/docs/svelte/await-expressions), 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`:
```js
/// file: svelte.config.js
export default {
kit: {
experimental: {
+++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`](#prerender) instead):
```js
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
// @filename: index.js
// ---cut---
import { query } from '$app/server';
import * as db from '$lib/server/database';
export const getPosts = query(async () => {
const posts = await db.sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return posts;
});
```
> [!NOTE] 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) that escapes any interpolated values.
The query returned from `getPosts` works as a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves to `posts`:
```svelte
```
Until the promise resolves — and if it errors — the nearest [``](../svelte/svelte-boundary) will be invoked.
While using `await` is recommended, as an alternative the query also has `loading`, `error` and `current` properties:
```svelte
{#if query.error}
{/if}
```
> [!NOTE] 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:
```svelte
{post.title}
{@html post.content}
```
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](https://standardschema.dev/) validation library such as [Zod](https://zod.dev/) or [Valibot](https://valibot.dev/):
```js
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
// @filename: index.js
// ---cut---
import * as v from 'valibot';
import { error } from '@sveltejs/kit';
import { query } from '$app/server';
import * as db from '$lib/server/database';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => {
const [post] = await db.sql`
SELECT * FROM post
WHERE slug = ${slug}
`;
if (!post) error(404, 'Not found');
return post;
});
```
Both the argument and the return value are serialized with [devalue](https://github.com/sveltejs/devalue), which handles types like `Date` and `Map` (and custom types defined in your [transport hook](hooks#Universal-hooks-transport)) in addition to JSON.
### Refreshing queries
Any query can be updated via its `refresh` method:
```svelte
```
> [!NOTE] 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`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)...
```ts
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
declare module '$lib/server/auth' {
interface User {
name: string;
}
/**
* Gets a user's info from their cookies, using `getRequestEvent`
*/
export function getUser(): Promise;
}
// @filename: index.js
// ---cut---
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
import * as db from '$lib/server/database';
import * as auth from '$lib/server/auth';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
export const createPost = form(async (data) => {
// Check the user is logged in
const user = await auth.getUser();
if (!user) error(401, 'Unauthorized');
const title = data.get('title');
const content = data.get('content');
// Check the data is valid
if (typeof title !== 'string' || typeof content !== 'string') {
error(400, 'Title and content are required');
}
const slug = title.toLowerCase().replace(/ /g, '-');
// Insert into the database
await db.sql`
INSERT INTO post (slug, title, content)
VALUES (${slug}, ${title}, ${content})
`;
// Redirect to the newly created page
redirect(303, `/blog/${slug}`);
});
```
...and returns an object that can be spread onto a `
```
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:
```js
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
const slug = '';
// ---cut---
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
export const createPost = form(async (data) => {
// form logic goes here...
// Refresh `getPosts()` on the server, and send
// the data back with the result of `createPost`
+++getPosts().refresh();+++
// Redirect to the newly created page
redirect(303, `/blog/${slug}`);
});
```
The second is to drive the single-flight mutation from the client, which we'll see in the section on [`enhance`](#form-enhance).
### Returns and redirects
The example above uses [`redirect(...)`](@sveltejs-kit#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`:
```ts
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
declare module '$lib/server/auth' {
interface User {
name: string;
}
/**
* Gets a user's info from their cookies, using `getRequestEvent`
*/
export function getUser(): Promise;
}
// @filename: index.js
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
import * as db from '$lib/server/database';
import * as auth from '$lib/server/auth';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
// ---cut---
export const createPost = form(async (data) => {
// ...
return { success: true };
});
```
```svelte
Create a new post
{#if createPost.result?.success}
Successfully published!
{/if}
```
This value is _ephemeral_ — it will vanish if you resubmit, navigate away, or reload the page.
> [!NOTE] 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:
```svelte
Create a new post
```
The callback receives the `form` element, the `data` it contains, and a `submit` function.
To enable client-driven [single-flight mutations](#form-Single-flight-mutations), use `submit().updates(...)`. For example, if the `getPosts()` query was used on this page, we could refresh it like so:
```ts
import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit';
interface Post {}
declare function submit(): Promise & {
updates(...queries: Array | RemoteQueryOverride>): Promise;
}
declare function getPosts(): RemoteQuery;
// ---cut---
await submit().updates(getPosts());
```
We can also _override_ the current data while the submission is ongoing:
```ts
import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit';
interface Post {}
declare function submit(): Promise & {
updates(...queries: Array | RemoteQueryOverride>): Promise;
}
declare function getPosts(): RemoteQuery;
declare const newPost: Post;
// ---cut---
await submit().updates(
getPosts().withOverride((posts) => [newPost, ...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 `