SvelteKit Remote Functions
Current Status
Remote functions are experimental in SvelteKit 2.58. Enable them in
svelte.config.js:
export default {
kit: { 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()
- 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
- 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.
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.
- Prefer
form() over command() where progressive enhancement matters.
- Use
prerender() for data that changes at most once per deployment.
- Last verified: SvelteKit 2.58.0, 2026-04-24
Reference Files
- references/remote-functions.md - Current patterns, examples, and gotchas
1---2name: sveltekit-remote-functions3description: SvelteKit remote functions guidance. Use for query(), form(), command(), and prerender() patterns in .remote.ts files.4---5
6# SvelteKit Remote Functions
7
8## Current Status
9
10Remote functions are **experimental** in SvelteKit 2.58. Enable them in
11`svelte.config.js`:
12
13```js
14export default {
15 kit: { experimental: { remoteFunctions: true } },
16 compilerOptions: { experimental: { async: true } } // only for await in components
17};
18```
19
20## Quick Start
21
22**File naming:** export remote functions from `*.remote.ts` or `*.remote.js`.
23Remote files can live anywhere under `src` except `src/lib/server`.
24
25**Which function?**
26
27- Dynamic reads → `query()`
28- Progressive forms → `form()`
29- Event-handler mutations → `command()`
30- Build-time/static reads → `prerender()`
31
32## Example
33
34```ts
35// posts.remote.ts
36import { command, query, requested } from '$app/server';
37import * as v from 'valibot';
38
39export const getPosts = query(v.object({ tag: v.optional(v.string()) }), async (filter) => {
40 return db.posts.find(filter);
41});
42
43export const createPost = command(v.object({ title: v.string() }), async (data) => {
44 await db.posts.create(data);
45
46 for (const { query } of requested(getPosts, 5)) {
47 void query.refresh();
48 }
49});
50```
51
52Client:
53
54```svelte
55<script lang="ts">
56 import { createPost, getPosts } from './posts.remote';
57
58 const posts = $derived(await getPosts({ tag: 'svelte' }));
59</script>
60
61<button onclick={() => createPost({ title: 'New' }).updates(getPosts)}>
62 Create
63</button>
64```
65
66## Current Rules
67
68- Remote functions always run on the server, even when called from the browser.
69- Args/returns use `devalue`; avoid functions, class instances, symbols, circular refs, and `RegExp`.
70- Validate exposed inputs with Standard Schema (`valibot`, `zod`, `arktype`, etc.) or use `.unchecked`/`'unchecked'` deliberately.
71- `query.batch()` batches calls from the same macrotask to solve n+1 reads.
72- `form().enhance()` `submit()` returns `true` when submission is valid/successful and `false` for validation failures.
73- `.updates()` is client-requested; server handlers must opt in with `requested(queryFn, limit)`.
74- `requested()` now yields `{ arg, query }`; call `query.refresh()`/`query.set(...)` on the bound instance.
75- `limit` is required for `requested()` to cap client-controlled refresh requests.
76- Inside command/form handlers, use `void query.refresh()`/`void query.set(value)`; SvelteKit awaits and serializes the updates.
77- Prefer `form()` over `command()` where progressive enhancement matters.
78- Use `prerender()` for data that changes at most once per deployment.
79- **Last verified:** SvelteKit 2.58.0, 2026-04-24
80
81## Reference Files
82
83- [references/remote-functions.md](references/remote-functions.md) - Current patterns, examples, and gotchas
84
85<!--
86PROGRESSIVE DISCLOSURE GUIDELINES:
87- Keep this file ~50 lines total (max ~150 lines)
88- Use 1-2 code blocks only (recommend 1)
89- Keep description <200 chars for Level 1 efficiency
90- Move detailed docs to references/ for Level 3 loading
91- This is Level 2 - quick reference ONLY, not a manual
92
93LLM WORKFLOW (when editing this file):
941. Write/edit SKILL.md
952. Format (if formatter available)
963. Run: npx skills add . --list
974. If the skill is not discovered, check SKILL.md frontmatter formatting
985. Validate again to confirm
99-->