SvelteKit Remote Functions
Current Status
Remote functions are experimental in SvelteKit 2.58. Prefer enabling them in vite.config.ts via the SvelteKit Vite plugin:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit({
experimental: { remoteFunctions: true },
compilerOptions: { experimental: { async: true } } // only for await in components
})
]
});
Quick Start
File naming: export remote functions from *.remote.ts or *.remote.js.
Remote files can live anywhere under src except src/lib/server.
Which function?
- Dynamic reads →
query()
- Realtime server streams →
query.live()
- Progressive forms →
form()
- Event-handler mutations →
command()
- Build-time/static reads →
prerender()
Example
// posts.remote.ts
import { command, query, requested } from "$app/server";
import * as v from "valibot";
export const getPosts = query(
v.object({ tag: v.optional(v.string()) }),
async (filter) => {
return db.posts.find(filter);
},
);
export const createPost = command(
v.object({ title: v.string() }),
async (data) => {
await db.posts.create(data);
for (const { query } of requested(getPosts, 5)) {
void query.refresh();
}
},
);
Client:
<script lang="ts">
import { createPost, getPosts } from './posts.remote';
const posts = $derived(await getPosts({ tag: 'svelte' }));
</script>
<button => createPost({ title: 'New' }).updates(getPosts)}>
Create
</button>
Current Rules
svelte.config.js still works in Kit 2, but Kit 3 will read Kit config from the Vite plugin instead.
- Remote functions always run on the server, even when called from the browser.
- Args/returns use
devalue; avoid functions, class instances, symbols, circular refs, and RegExp.
- Validate exposed inputs with Standard Schema (
valibot, zod, arktype, etc.) or use .unchecked/'unchecked' deliberately.
query.batch() batches calls from the same macrotask to solve n+1 reads.
query.live() returns an async iterable; SSR uses the first yield, clients stay connected while rendered.
- Prefer event-driven live queries over polling when a mutation can notify listeners (
Promise.withResolvers()/pubsub).
- Live queries expose
connected and reconnect(), but no refresh(); .run() returns Promise<AsyncGenerator<T>>.
- Do not service-worker-cache live query responses; exclude
Cache-Control: no-store streams.
form().enhance() submit() returns true when submission is valid/successful and false for validation failures.
.updates() is client-requested; server handlers must opt in with requested(queryFn, limit).
requested() now yields { arg, query }; call query.refresh()/query.set(...) on the bound instance.
limit is required for requested() to cap client-controlled refresh requests.
- Inside command/form handlers, use
void query.refresh()/void query.set(value); SvelteKit awaits and serializes the updates.
- For live queries, single-flight mutations can call
void live_query.reconnect().
- Prefer
form() over command() where progressive enhancement matters.
- Use
prerender() for data that changes at most once per deployment.
- Last verified: 2026-06-08
Reference Files
- references/remote-functions.md - Current patterns, examples, and gotchas
1---2name: sveltekit-remote-functions-23description: Implement SvelteKit remote functions. Use for query(), query.live(), form(), command(), and prerender() patterns in .remote.ts files.4---56# SvelteKit Remote Functions78## Current Status910Remote functions are **experimental** in SvelteKit 2.58. Prefer enabling them in `vite.config.ts` via the SvelteKit Vite plugin:1112```ts13import { sveltekit } from '@sveltejs/kit/vite';14import { defineConfig } from 'vite';1516export default defineConfig({17 plugins: [18 sveltekit({19 experimental: { remoteFunctions: true },20 compilerOptions: { experimental: { async: true } } // only for await in components21 })22 ]23});24```2526## Quick Start2728**File naming:** export remote functions from `*.remote.ts` or `*.remote.js`.29Remote files can live anywhere under `src` except `src/lib/server`.3031**Which function?**3233- Dynamic reads → `query()`34- Realtime server streams → `query.live()`35- Progressive forms → `form()`36- Event-handler mutations → `command()`37- Build-time/static reads → `prerender()`3839## Example4041```ts42// posts.remote.ts43import { command, query, requested } from "$app/server";44import * as v from "valibot";4546export const getPosts = query(47 v.object({ tag: v.optional(v.string()) }),48 async (filter) => {49 return db.posts.find(filter);50 },51);5253export const createPost = command(54 v.object({ title: v.string() }),55 async (data) => {56 await db.posts.create(data);5758 for (const { query } of requested(getPosts, 5)) {59 void query.refresh();60 }61 },62);63```6465Client:6667```svelte68<script lang="ts">69 import { createPost, getPosts } from './posts.remote';7071 const posts = $derived(await getPosts({ tag: 'svelte' }));72</script>7374<button onclick={() => createPost({ title: 'New' }).updates(getPosts)}>75 Create76</button>77```7879## Current Rules8081- `svelte.config.js` still works in Kit 2, but Kit 3 will read Kit config from the Vite plugin instead.82- Remote functions always run on the server, even when called from the browser.83- Args/returns use `devalue`; avoid functions, class instances, symbols, circular refs, and `RegExp`.84- Validate exposed inputs with Standard Schema (`valibot`, `zod`, `arktype`, etc.) or use `.unchecked`/`'unchecked'` deliberately.85- `query.batch()` batches calls from the same macrotask to solve n+1 reads.86- `query.live()` returns an async iterable; SSR uses the first yield, clients stay connected while rendered.87- Prefer event-driven live queries over polling when a mutation can notify listeners (`Promise.withResolvers()`/pubsub).88- Live queries expose `connected` and `reconnect()`, but no `refresh()`; `.run()` returns `Promise<AsyncGenerator<T>>`.89- Do not service-worker-cache live query responses; exclude `Cache-Control: no-store` streams.90- `form().enhance()` `submit()` returns `true` when submission is valid/successful and `false` for validation failures.91- `.updates()` is client-requested; server handlers must opt in with `requested(queryFn, limit)`.92- `requested()` now yields `{ arg, query }`; call `query.refresh()`/`query.set(...)` on the bound instance.93- `limit` is required for `requested()` to cap client-controlled refresh requests.94- Inside command/form handlers, use `void query.refresh()`/`void query.set(value)`; SvelteKit awaits and serializes the updates.95- For live queries, single-flight mutations can call `void live_query.reconnect()`.96- Prefer `form()` over `command()` where progressive enhancement matters.97- Use `prerender()` for data that changes at most once per deployment.98- **Last verified:** 2026-06-0899100## Reference Files101102- [references/remote-functions.md](references/remote-functions.md) - Current patterns, examples, and gotchas103104<!--105PROGRESSIVE DISCLOSURE GUIDELINES:106- Keep this file ~50 lines total (max ~150 lines)107- Use 1-2 code blocks only (recommend 1)108- Keep description <200 chars for Level 1 efficiency109- Move detailed docs to references/ for Level 3 loading110- This is Level 2 - quick reference ONLY, not a manual111112LLM WORKFLOW (when editing this file):1131. Write/edit SKILL.md1142. Format (if formatter available)1153. Run: npx skills add . --list1164. If the skill is not discovered, check SKILL.md frontmatter formatting1175. Validate again to confirm118-->