Type-safe communication between client and server with remote functions
Available since SvelteKit 2.27. This feature is experimental and subject to change without notice.
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 containing things like environment variables and database clients.
The query function allows you to read dynamic data from the server:
Copy
/// file: src/routes/blog/data.remote.jsimport { 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;});
Until the promise resolves — and if it errors — the nearest <svelte:boundary> will be invoked.
Query functions can accept an argument. Use a Standard Schema validation library like Zod or Valibot to validate:
Copy
/// file: src/routes/blog/data.remote.jsimport * as v from 'valibot';import { error } from '@sveltejs/kit';import { query } from '$app/server';import * as db from '$lib/server/database';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;});
Use it in your component:
Copy
<!--- file: src/routes/blog/[slug]/+page.svelte ---><script> import { getPost } from '../data.remote'; let { params } = $props(); const post = $derived(await getPost(params.slug));</script><h1>{post.title}</h1><div>{@html post.content}</div>
The form function makes it easy to write data to the server:
Copy
/// file: src/routes/blog/data.remote.jsimport * as v from 'valibot';import { error, redirect } from '@sveltejs/kit';import { form } from '$app/server';import * as db from '$lib/server/database';import * as auth from '$lib/server/auth';export const createPost = form( v.object({ title: v.pipe(v.string(), v.nonEmpty()), content: v.pipe(v.string(), v.nonEmpty()) }), async ({ title, content }) => { const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); const slug = title.toLowerCase().replace(/ /g, '-'); await db.sql` INSERT INTO post (slug, title, content) VALUES (${slug}, ${title}, ${content}) `; redirect(303, `/blog/${slug}`); });
The form object contains method and action properties that allow it to work without JavaScript. It also has an attachment that progressively enhances the form when JavaScript is available.
The prerender function is similar to query, except it’s invoked at build time:
Copy
/// file: src/routes/blog/data.remote.jsimport { 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;});
Use this for data that changes at most once per redeployment. You can use prerender functions on pages that are otherwise dynamic, allowing for partial prerendering.