Patterns for SvelteKit remote functions (query, command, form, batch) from $app/server. Use when working with .remote.ts files, server-side data fetching with query(), mutations with command(), form handling with form(), request batching, or when users ask about calling server functions from client code, type-safe RPC, or replacing load functions with remote functions.
Enables type-safe server functions from client components with query, command, and form patterns.
npx claudepluginhub maxnoller/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/auth.remote.tsexamples/todos.remote.tsRemote functions allow calling server-side code directly from client components with full type safety. They replace many use cases for load functions and form actions.
Status: Experimental (requires configuration)
Enable in svelte.config.js:
const config = {
kit: {
experimental: { remoteFunctions: true }
},
compilerOptions: {
experimental: { async: true }
}
};
export default config;
Create read-only server queries:
// src/lib/todos.remote.ts
import { query } from '$app/server';
import { db } from '$lib/db';
export const getTodos = query(async () => {
return await db.todos.findMany();
});
export const getTodoById = query(async (id: string) => {
return await db.todos.findUnique({ where: { id } });
});
Use in components:
<script>
import { getTodos } from '$lib/todos.remote';
let todos = $state<Todo[]>([]);
$effect(() => {
getTodos().then(data => todos = data);
});
</script>
How it works:
fetch() callCreate server-side mutations:
// src/lib/todos.remote.ts
import { command } from '$app/server';
import { db } from '$lib/db';
export const createTodo = command(async (text: string) => {
return await db.todos.create({ data: { text, done: false } });
});
export const toggleTodo = command(async (id: string) => {
const todo = await db.todos.findUnique({ where: { id } });
return await db.todos.update({
where: { id },
data: { done: !todo?.done }
});
});
export const deleteTodo = command(async (id: string) => {
await db.todos.delete({ where: { id } });
});
Use in components:
<script>
import { createTodo, toggleTodo, deleteTodo } from '$lib/todos.remote';
let newText = $state('');
async function handleSubmit() {
await createTodo(newText);
newText = '';
// Refetch or invalidate
}
</script>
<form onsubmit={handleSubmit}>
<input bind:value={newText} />
<button>Add</button>
</form>
Create forms that work with and without JavaScript:
// src/lib/auth.remote.ts
import { form } from '$app/server';
import { fail, redirect } from '@sveltejs/kit';
export const login = form(async ({ request }) => {
const data = await request.formData();
const email = data.get('email') as string;
const password = data.get('password') as string;
if (!email || !password) {
return fail(400, { error: 'Email and password required' });
}
const user = await authenticate(email, password);
if (!user) {
return fail(401, { error: 'Invalid credentials', email });
}
redirect(303, '/dashboard');
});
Use in components:
<script>
import { login } from '$lib/auth.remote';
</script>
<form {...login.spread()}>
<input name="email" type="email" />
<input name="password" type="password" />
<button>Login</button>
</form>
Progressive enhancement:
Batch multiple queries into a single HTTP request (SvelteKit 2.38+):
// src/lib/data.remote.ts
import { query } from '$app/server';
export const getUserById = query(async (id: string) => {
return await db.users.findUnique({ where: { id } });
});
// Enable batching
export const getUserByIdBatched = query.batch(
async (ids: string[]) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
return (id: string) => users.find(u => u.id === id);
}
);
Use in components:
<script>
import { getUserByIdBatched } from '$lib/data.remote';
// These calls are batched into one request
let user1 = getUserByIdBatched('1');
let user2 = getUserByIdBatched('2');
let user3 = getUserByIdBatched('3');
</script>
{#await Promise.all([user1, user2, user3])}
Loading...
{:then [u1, u2, u3]}
{u1.name}, {u2.name}, {u3.name}
{/await}
// src/lib/contact.remote.ts
import { form } from '$app/server';
import { fail } from '@sveltejs/kit';
import { z } from 'zod';
const ContactSchema = z.object({
name: z.string().min(1, 'Name required'),
email: z.string().email('Invalid email'),
message: z.string().min(10, 'Message too short')
});
export const submitContact = form(async ({ request }) => {
const formData = await request.formData();
const data = Object.fromEntries(formData);
const result = ContactSchema.safeParse(data);
if (!result.success) {
return fail(400, {
errors: result.error.flatten().fieldErrors,
data
});
}
await sendEmail(result.data);
return { success: true };
});
<script>
import { submitContact } from '$lib/contact.remote';
let form = $state({
name: '',
email: '',
message: ''
});
let errors = $state<Record<string, string[]>>({});
let submitting = $state(false);
async function handleSubmit(e: Event) {
e.preventDefault();
submitting = true;
errors = {};
const formData = new FormData(e.target as HTMLFormElement);
const result = await submitContact.call(formData);
if (result?.errors) {
errors = result.errors;
}
submitting = false;
}
</script>
<form onsubmit={handleSubmit}>
<input name="name" bind:value={form.name} />
{#if errors.name}<span class="error">{errors.name[0]}</span>{/if}
<input name="email" type="email" bind:value={form.email} />
{#if errors.email}<span class="error">{errors.email[0]}</span>{/if}
<textarea name="message" bind:value={form.message}></textarea>
{#if errors.message}<span class="error">{errors.message[0]}</span>{/if}
<button disabled={submitting}>
{submitting ? 'Sending...' : 'Send'}
</button>
</form>
Place .remote.ts files anywhere in src/ except src/lib/server/:
src/
├── lib/
│ ├── todos.remote.ts # Todo operations
│ ├── auth.remote.ts # Authentication
│ └── server/ # Server-only (cannot use .remote.ts here)
│ └── db.ts
├── routes/
│ └── api/
│ └── users.remote.ts # Can colocate with routes
Use remote functions when:
Use load functions when:
| Feature | Remote Functions | tRPC |
|---|---|---|
| Setup | Built-in (config flag) | Separate package |
| Type safety | Full | Full |
| File convention | .remote.ts | Routers |
| Batching | Native | Plugin |
| Forms | form() helper | Manual |
| Learning curve | Low (SvelteKit native) | Medium |
.remote.ts in src/lib/server/ - It won't workfail() for expected errorsActivates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.