Available since 2.27

Remote functions are a tool for type-safe communication between client and server. They can be _called_ anywhere in your app, but always _run_ on the server, meaning they can safely access [server-only modules](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[]): 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

Recent posts

``` 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

Recent posts

{#if query.error}

oops!

{:else if query.loading}

loading...

{:else} {/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. > [!NOTE] For `query` and `prerender` arguments (but not return values), objects, maps, and sets are sorted so that instances with the same members result in the same cache key. For example, `getPosts({ limit: 10, offset: 10 })` and `getPosts({ offset: 10, limit: 10 })` will result in the same cache key. If order is important to you, you'll have to use an array. ### Refreshing queries Any query can be re-fetched via its `refresh` method, which retrieves the latest value from the server: ```svelte ``` > [!NOTE] Queries are cached while they're on the page, meaning `getPosts() === getPosts()`. This means you don't need a reference like `const posts = getPosts()` in order to update the query. ## query.batch `query.batch` works like `query` except that it batches requests that happen within the same macrotask. This solves the so-called n+1 problem: rather than each query resulting in a separate database call (for example), simultaneous queries are grouped together. On the server, the callback receives an array of the arguments the function was called with. It must return a function of the form `(input: Input, index: number) => Output`. SvelteKit will then call this with each of the input arguments to resolve the individual calls with their results. ```js /// file: weather.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 { query } from '$app/server'; import * as db from '$lib/server/database'; export const getWeather = query.batch(v.string(), async (cityIds) => { const weather = await db.sql` SELECT * FROM weather WHERE city_id = ANY(${cityIds}) `; const lookup = new Map(weather.map(w => [w.city_id, w])); return (cityId) => lookup.get(cityId); }); ``` ```svelte

Weather

{#each cities.slice(0, limit) as city}

{city.name}

{/each} {#if cities.length > limit} {/if} ``` ## form The `form` function makes it easy to write data to the server. It takes a callback that receives `data` constructed from the submitted [`FormData`](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( v.object({ title: v.pipe(v.string(), v.nonEmpty()), content:v.pipe(v.string(), v.nonEmpty()) }), async ({ title, content }) => { // Check the user is logged in const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); 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 `
` element. The callback is called whenever the form is submitted. ```svelte

Create a new post

``` The form object contains `method` and `action` properties that allow it to work without JavaScript (i.e. it submits data and reloads the page). It also has an [attachment](/docs/svelte/@attach) that progressively enhances the form when JavaScript is available, submitting data *without* reloading the entire page. As with `query`, if the callback uses the submitted `data`, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `form`. ### Fields A form is composed of a set of _fields_, which are defined by the schema. In the case of `createPost`, we have two fields, `title` and `content`, which are both strings. To get the attributes for a field, call its `.as(...)` method, specifying which [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types) to use. For most input types, you can also pass a second argument — `.as(type, value)` — to control the rendered value: ```svelte
``` These attributes allow SvelteKit to set the correct input type, set a `name` that is used to construct the `data` passed to the handler, populate the `value` of the form (for example following a failed submission, to save the user having to re-enter everything), and set the [`aria-invalid`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) state. Passing a second argument to `.as(...)` is useful when rendering a form from existing data, such as an edit form or multiple instances created with [`for(...)`](#form-Multiple-instances-of-a-form). `radio`, `submit` and `hidden` inputs always need this value, and `checkbox` inputs need it when they represent one option in an array field. `file` inputs cannot be populated this way. > [!NOTE] The generated `name` attribute uses JS object notation (e.g. `nested.array[0].value`). String keys that require quotes such as `object['nested-array'][0].value` are not supported. Under the hood, boolean checkbox and number field names are prefixed with `b:` and `n:`, respectively, to signal SvelteKit to coerce the values from strings prior to validation. Fields can be nested in objects and arrays, and their values can be strings, numbers, booleans or `File` objects. For example, if your schema looked like this... ```js /// file: data.remote.js import * as v from 'valibot'; import { form } from '$app/server'; // ---cut--- const datingProfile = v.object({ name: v.string(), photo: v.file(), info: v.object({ height: v.number(), likesDogs: v.optional(v.boolean(), false) }), attributes: v.array(v.string()) }); export const createProfile = form(datingProfile, (data) => { /* ... */ }); ``` ...your form could look like this: ```svelte

My best attributes

``` Because our form contains a `file` input, we've added an `enctype="multipart/form-data"` attribute. The values for `info.height` and `info.likesDogs` are coerced to a number and a boolean respectively. > [!NOTE] If a `checkbox` input is unchecked, the value is not included in the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object that SvelteKit constructs the data from. As such, we have to make the value optional in our schema. In Valibot that means using `v.optional(v.boolean(), false)` instead of just `v.boolean()`, whereas in Zod it would mean using `z.coerce.boolean()`. In the case of `radio` and `checkbox` inputs that all belong to the same field, the `value` must be specified as a second argument to `.as(...)`: ```js /// file: constants.js export const operatingSystems = /** @type {const} */ (['windows', 'mac', 'linux']); export const languages = /** @type {const} */ (['html', 'css', 'js']); ``` ```js /// file: data.remote.js // @filename: constants.js export const operatingSystems = /** @type {const} */ (['windows', 'mac', 'linux']); export const languages = /** @type {const} */ (['html', 'css', 'js']); // @filename: index.js import * as v from 'valibot'; import { form } from '$app/server'; // ---cut--- import { operatingSystems, languages } from './constants'; export const survey = form( v.object({ operatingSystem: v.picklist(operatingSystems), languages: v.optional(v.array(v.picklist(languages)), []), }), (data) => { /* ... */ }, ); ``` ```svelte

Which operating system do you use?

{#each operatingSystems as os} {/each}

Which languages do you write code in?

{#each languages as language} {/each}
``` Alternatively, you could use `select` and `select multiple`: ```svelte

Which operating system do you use?

Which languages do you write code in?

``` > [!NOTE] As with unchecked `checkbox` inputs, if no selections are made then the data will be `undefined`. For this reason, the `languages` field uses `v.optional(v.array(...), [])` rather than just `v.array(...)`. ### Programmatic validation In addition to declarative schema validation, you can programmatically mark fields as invalid inside the form handler using the `invalid` helper from `@sveltejs/kit`. This is useful for cases where you can't know if something is valid until you try to perform some action. - It throws just like `redirect` or `error` - It accepts multiple arguments that can be strings (for issues relating to the form as a whole — these will only show up in `fields.allIssues()`) or standard-schema-compliant issues (for those relating to a specific field). Use the `issue` parameter for type-safe creation of such issues: ```js /// file: src/routes/shop/data.remote.js import * as v from 'valibot'; import { invalid } from '@sveltejs/kit'; import { form } from '$app/server'; import * as db from '$lib/server/database'; export const buyHotcakes = form( v.object({ qty: v.pipe( v.number(), v.minValue(1, 'you must buy at least one hotcake') ) }), async (data, issue) => { try { await db.buy(data.qty); } catch (e) { if (e.code === 'OUT_OF_STOCK') { invalid( issue.qty(`we don't have enough hotcakes`) ); } } } ); ``` ### Validation If the submitted data doesn't pass the schema, the callback will not run. Instead, each invalid field's `issues()` method will return an array of `{ message: string }` objects, and the `aria-invalid` attribute (returned from `as(...)`) will be set to `true`: ```svelte
``` You don't need to wait until the form is submitted to validate the data — you can call `validate()` programmatically, for example in an `oninput` callback (which will validate the data on every keystroke) or an `onchange` callback: ```svelte
createPost.validate()}>
``` By default, issues will be ignored if they belong to form controls that haven't yet been interacted with. To validate _all_ inputs, call `validate({ includeUntouched: true })`. For client-side validation, you can specify a _preflight_ schema which will populate `issues()` and prevent data being sent to the server if the data doesn't validate: ```svelte

Create a new post

``` > [!NOTE] The preflight schema can be the same object as your server-side schema, if appropriate, though it won't be able to do server-side checks like 'this value already exists in the database'. Note that you cannot export a schema from a `.remote.ts` or `.remote.js` file, so the schema must either be exported from a shared module, or from a ` ``` ### Handling sensitive data In the case of a non-progressively-enhanced form submission (i.e. where JavaScript is unavailable, for whatever reason) `value()` is also populated if the submitted data is invalid, so that the user does not need to fill the entire form out from scratch. You can prevent sensitive data (such as passwords and credit card numbers) from being sent back to the user by using a name with a leading underscore: ```svelte
``` In this example, if the data does not validate, only the first `` will be populated when the page reloads. ### 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( v.object({/* ... */}), 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

{ try { if (await form.submit()) { form.element.reset(); showToast('Successfully published!'); } else { showToast('Invalid data!'); } } catch (error) { showToast('Oh no! Something went wrong'); } })}>
``` > When using `enhance`, the `
` is not automatically reset — you must call `form.element.reset()` if you want to clear the inputs. The callback receives a copy of the form instance. It has all the same properties and methods except `enhance`, and `form.submit()` performs the submission directly without re-running the enhance callback. Inside the callback, `form.element` is always defined. ### Multiple instances of a form Some forms may be repeated as part of a list. In this case you can create separate instances of a form function via `for(id)` to achieve isolation. When each instance should render different values, pass them as the second argument to `.as(...)`: ```svelte

Todos

{#each await getTodos() as todo} {@const modify = modifyTodo.for(todo.id)}
{/each} ``` ### Multiple submit buttons It's possible for a `
` to have multiple submit buttons. For example, you might have a single form that allows you to log in or register depending on which button was clicked. To accomplish this, add a field to your schema for the button value, and use `as('submit', value)` to bind it: ```svelte
``` In your form handler, you can check which button was clicked: ```js /// file: $lib/auth.js import * as v from 'valibot'; import { form } from '$app/server'; export const loginOrRegister = form( v.object({ username: v.string(), _password: v.string(), action: v.picklist(['login', 'register']) }), async ({ username, _password, action }) => { if (action === 'login') { // handle login } else { // handle registration } } ); ``` ## 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. > [!NOTE] Prefer `form` where possible, since it gracefully degrades if JavaScript is disabled or fails to load. As with `query` and `form`, if the function accepts an argument, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `command`. ```ts /// file: likes.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 { query, command } from '$app/server'; import * as db from '$lib/server/database'; export const getLikes = query(v.string(), async (id) => { const [row] = await db.sql` SELECT likes FROM item WHERE id = ${id} `; return row.likes; }); export const addLike = command(v.string(), async (id) => { await db.sql` UPDATE item SET likes = likes + 1 WHERE id = ${id} `; }); ``` Now simply call `addLike`, from (for example) an event handler: ```svelte

likes: {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. ### 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 & { updates(...updates: RemoteQueryUpdate[]): Promise; } declare function getPosts(args: { filter: string }): RemoteQuery; declare const newPost: Post; // ---cut--- await submit().updates( // to request all active instances of getPosts getPosts, // to request a specific instance getPosts({ filter: 'author:santa' }), // to request a specific instance with an optimistic override getPosts({ filter: 'author:santa' }).withOverride((posts) => [newPost, ...posts]) ); ``` It's not enough to just request the updates from the client -- you need to accept them from the server as well: ```js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; const slug = ''; const post = { id: '' }; /** @type {any} */ const externalApi = ''; // ---cut--- import { query, form, requested } from '$app/server'; export const getPosts = query(v.object({ filter: v.string() }), async ({ filter }) => { /* ... */ }); export const createPost = form( v.object({/* ... */}), async (data) => { // form logic goes here... +++for (const arg of requested(getPosts, 1)) {+++ +++ void getPosts(arg).refresh();+++ +++}+++ // Redirect to the newly created page redirect(303, `/blog/${slug}`); } ); ``` `requested` gives you access to the requested query arguments for the supplied query. It returns the *parsed* arguments for the query -- when these arguments are passed back into the query in `getPosts(arg).refresh()`, they will not be parsed again. If parsing an argument fails, that query will error, but the entire command will not fail. `requested`'s second parameter, `limit`, is the maximum number of items it will return. Any refresh requests beyond this limit will fail. Additionally, `requested` allows a simple shorthand when all you want to do is refresh the requested query instances: ```ts import type { RemoteQueryFunction } from '@sveltejs/kit'; import { requested } from '$app/server'; declare const getPosts: RemoteQueryFunction; // ---cut--- // this is the same as looping over the result and calling `void getPosts(arg).refresh()`. await requested(getPosts, 1).refreshAll(); ``` ## 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. ```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 { prerender } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = prerender(async () => { const posts = await db.sql` SELECT title, slug FROM post ORDER BY published_at DESC `; return 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`](https://developer.mozilla.org/en-US/docs/Web/API/Cache) API. This cache survives page reloads, and will be cleared when the user first visits a new deployment of your app. > [!NOTE] 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](#query-Query-arguments) with a [Standard Schema](https://standardschema.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 { prerender } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = prerender(async () => { /* ... */ }); export const getPost = prerender(v.string(), async (slug) => { const [post] = await db.sql` SELECT * FROM post WHERE slug = ${slug} `; if (!post) error(404, 'Not found'); return post; }); ``` Any calls to `getPost(...)` found by SvelteKit's crawler while [prerendering pages](page-options#prerender) will be saved automatically, but you can also specify which values it should be called with using the `inputs` option: ```js /// file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { prerender } from '$app/server'; // ---cut--- export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { inputs: () => [ 'first-post', 'second-post', 'third-post' ] } ); ``` 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: ```js /// file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { prerender } from '$app/server'; // ---cut--- export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { +++dynamic: true+++, 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](https://http.dog/400) response. You can control the message by implementing the [`handleValidationError`](hooks#Server-hooks-handleValidationError) server hook, which, like [`handleError`](hooks#Shared-hooks-handleError), must return an [`App.Error`](errors#Type-safety) (which defaults to `{ message: string }`): ```js /// file: src/hooks.server.ts /** @type {import('@sveltejs/kit').HandleValidationError} */ export function handleValidationError({ event, issues }) { return { 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: ```ts /// file: data.remote.ts import { query } from '$app/server'; export const getStuff = query('unchecked', async ({ id }: { id: string }) => { // the shape might not actually be what TypeScript thinks // since bad actors might call this function with other arguments }); ``` ## Using `getRequestEvent` Inside `query`, `form` and `command` you can use [`getRequestEvent`]($app-server#getRequestEvent) to get the current [`RequestEvent`](@sveltejs-kit#RequestEvent) object. This makes it easy to build abstractions for interacting with cookies, for example: ```ts /// file: user.remote.ts import { getRequestEvent, query } from '$app/server'; import { findUser } from '$lib/server/database'; export const getProfile = query(async () => { const user = await getUser(); return { name: user.name, avatar: user.avatar }; }); // this query could be called from multiple places, but // the function will only run once per request const getUser = query(async () => { const { cookies } = getRequestEvent(); return await findUser(cookies.get('session_id')); }); ``` Note that some properties of `RequestEvent` are different inside remote functions: - you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions) - `route`, `params` and `url` relate to the page the remote function was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Queries are not re-run when the user navigates (unless the argument to the query changes as a result of navigation), and so you should be mindful of how you use these values. In particular, never use them to determine whether or not a user is authorized to access certain data. ## Redirects Inside `query`, `form` and `prerender` functions it is possible to use the [`redirect(...)`](@sveltejs-kit#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.)