Remote functions are a tool for type-safe communication between client and server. They can be _called_ anywhere in your app, but always _run_ on the server, meaning they can safely access [server-only modules](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` and optionally, the `compilerOptions.experimental.async` option to use `await` in components: ```js /// file: svelte.config.js /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { experimental: { +++remoteFunctions: true+++ } }, compilerOptions: { experimental: { +++async: true+++ } } }; export default config; ``` ## Overview Remote functions are exported from a `.remote.js` or `.remote.ts` file, and come in four flavours: `query`, `form`, `command` and `prerender`. On the client, the exported functions are transformed to `fetch` wrappers that invoke their counterparts on the server via a generated HTTP endpoint. Remote files can be placed anywhere in your `src` directory (except inside the `src/lib/server` directory), and third party libraries can provide them, too. ## 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[]): PromiseAvailable since 2.27
oops!
{:else if query.loading}loading...
{:else}{await time}
connected: {time.connected}
``` If the connection drops, `connected` becomes `false`. SvelteKit will attempt to reconnect passively, with exponential backoff, and actively if `navigator.onLine` goes from `false` to `true`. Unlike `query`, live queries do not have a `refresh()` method, as they are self-updating. As with `query` and `query.batch`, call `.run()` outside render when you need imperative access. For live queries, `run()` returns a `PromiseSuccessfully 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: ```sveltelikes: {await getLikes(item.id)}
``` > [!NOTE] Commands cannot be called during render. ## Single-flight mutations The purpose of both [`form`](#form) and [`command`](#command) is *mutating data*. In many cases, mutating data invalidates other data. By default, `form` deals with this by automatically invalidating all queries and load functions following a successful submission, to emulate what would happen with a traditional full-page reload. `command`, on the other hand, does nothing. Typically, neither of these options is going to be the ideal solution — invalidating everything is likely wasteful, as it's unlikely a form submission changed *everything* being displayed on your webpage. In the case of `command`, doing nothing likely *under*-invalidates your app, leaving stale data displayed. In both cases, it's common to have to perform two round-trips to the server: One to run the mutation, and another after that completes to re-request the data from any queries you need to refresh. SvelteKit solves both of these problems with *single-flight mutations*: Your `form` submission or `command` invocation can refresh queries and pass their results back to the client in a single request. ### Server-driven refreshes In most circumstances, the server handler knows what client data needs to be updated based on its arguments: ```js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { query, form } from '$app/server'; const slug = ''; const post = { id: '' }; /** @type {any} */ const externalApi = ''; // ---cut--- export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { /* ... */ }); export const createPost = form( v.object({/* ... */}), async (data) => { // form logic goes here... // Refresh `getPosts()` on the server, and send // the data back with the result of `createPost` // it's safe to throw away the promise from `refresh`, // as the framework awaits it for us before serving the response +++void getPosts().refresh();+++ // Redirect to the newly created page redirect(303, `/blog/${slug}`); } ); export const updatePost = form( v.object({ id: v.string() }), async (post) => { // form logic goes here... const result = externalApi.update(post); // The API already gives us the updated post, // no need to refresh it, we can set it directly +++getPost(post.id).set(result);+++ } ); ``` Because queries are keyed based on their arguments, `await getPost(post.id).set(result)` on the server knows to look up the matching `getPost(id)` on the client to update it. The same goes for `getPosts().refresh()` -- it knows to look up `getPosts()` with no argument on the client. ### Reconnecting live queries in mutations Single-flight mutations can also reconnect `query.live` instances. In a `form`/`command` handler, call `.reconnect()` on the live query resource you want to reconnect: ```js import * as v from 'valibot'; import { form, query } from '$app/server'; export const getNotifications = query.live(v.string(), async function* (userId) { while (true) { yield await db.notifications(userId); await wait(1000); } }); export const markAllRead = form(v.object({ userId: v.string() }), async ({ userId }) => { // mutation logic... +++getNotifications(userId).reconnect();+++ }); ``` This schedules a reconnect for the matching active client instances and applies it as part of the mutation response (i.e. in the same flight as the form/command result). ### Client-requested refreshes Unfortunately, life isn't always as simple as the preceding example. The server always knows which query _functions_ to update, but it may not know which specific query _instances_ to update. For example, if `getPosts({ filter: 'author:santa' })` is rendered on the client, calling `getPosts().refresh()` in the server handler won't update it. You'd need to call `getPosts({ filter: 'author:santa' }).refresh()` instead — but how could you know which specific combinations of filters are currently rendered on the client, especially if your query argument is more complicated than an object with just one key? SvelteKit makes this easy by allowing the client to _request_ that the server updates specific data using `submit().updates` (for `form`) or `myCommand().updates` (for `command`): ```ts import type { RemoteQueryUpdate, RemoteQuery } from '@sveltejs/kit'; interface Post {} declare function submit(): Promise