Introduction
Getting started with Svelte
#Automatic Setup With Create Instant App
The fastest way to get started with Instant with SvelteKit is to use create-instant-app to scaffold a new project with Instant already set up.
To get started run:
npx create-instant-app --sv
#Manual Setup
Create a blank SvelteKit app:
npx sv create my-app
Add the InstantDB Svelte Library:
npm i @instantdb/svelte
Use instant-cli to set up a new Instant project. This will prompt you to log in if you haven't already. It will then create a schema file, permissions file, and update your .env file.
npx instant-cli init
Create a database client in src/lib/db.ts:
import { init } from '@instantdb/svelte';import schema from '../instant.schema';export const db = init({appId: import.meta.env.VITE_INSTANT_APP_ID!,schema,useDateObjects: true,});
You're now ready to make queries and transactions to your database!
#Creating a To-Do List App
Let's add a "todo" entity to our schema file at src/instant.schema.ts:
import { i } from '@instantdb/svelte';const _schema = i.schema({entities: {$files: i.entity({path: i.string().unique().indexed(),url: i.string(),}),$users: i.entity({email: i.string().unique().indexed().optional(),imageURL: i.string().optional(),type: i.string().optional(),}),todos: i.entity({text: i.string(),done: i.boolean(),createdAt: i.date(),}),},links: {$usersLinkedPrimaryUser: {forward: {on: '$users',has: 'one',label: 'linkedPrimaryUser',onDelete: 'cascade',},reverse: {on: '$users',has: 'many',label: 'linkedGuestUsers',},},},rooms: {},});//...
Push the schema:
npx instant-cli push
Instant doesn't support SSR with SvelteKit yet (let us know if you want this!) so you need to disable SSR for routes that use Instant.
export const ssr = false;
Replace the content of src/routes/+page.svelte with the following:
<script lang="ts">import { id, type InstaQLEntity } from '@instantdb/svelte';import { db } from '$lib/db';import type { AppSchema } from '../instant.schema';type Todo = InstaQLEntity<AppSchema, 'todos'>;const query = db.useQuery({ todos: {} });let text = $state('');function addTodo(todoText: string) {db.transact(db.tx.todos[id()].update({text: todoText,done: false,createdAt: Date.now(),}),);}function deleteTodo(todo: Todo) {db.transact(db.tx.todos[todo.id].delete());}function toggleDone(todo: Todo) {db.transact(db.tx.todos[todo.id].update({ done: !todo.done }));}function deleteCompleted(todos: Todo[]) {const completed = todos.filter((todo) => todo.done);if (completed.length === 0) return;const txs = completed.map((todo) => db.tx.todos[todo.id].delete());db.transact(txs);}</script>{#if query.isLoading}<div>Loading...</div>{:else if query.error}<div>Error: {query.error.message}</div>{:else} {@const todos = query.data?.todos ?? []}<div><h2>Todos</h2><div><form onsubmit={(e) => {e.preventDefault(); if (!text) return; addTodo(text); text = '';}}><inputbind:value={text}autofocusplaceholder="What needs to be done?"type="text"/></form>{#each todos as todo (todo.id)}<div><input type="checkbox" checked={todo.done} onchange={() =>toggleDone(todo)} /><span class:line-through={todo.done}>{todo.text}</span><button onclick={() => deleteTodo(todo)}>X</button></div>{/each}<div>Remaining todos: {todos.filter((t) => !t.done).length}<button onclick={() =>deleteCompleted(todos)}> Delete Completed</button></div></div></div>{/if}
Go to localhost:5173, and huzzah 🎉 You've got a fully functional todo list running!
#Reactivity
In Svelte 5, Instant's hooks return $state proxies. You read properties directly off the returned object, and Svelte automatically tracks them for re-rendering.
<script lang="ts">const query = db.useQuery({ todos: {} });// query.isLoading, query.data, query.error are all reactive</script>{#if !query.isLoading}<p>{query.data?.todos.length} todos</p>{/if}
For primitive values like connection status and local IDs, hooks return a { current: value } wrapper (since primitives cannot be $state proxies):
<script lang="ts">const status = db.useConnectionStatus();// status.current is reactive</script><p>Connection: {status.current}</p>
#Reactive and conditional queries
You can pass a function to useQuery to make queries reactive or conditional. The function will be re-evaluated whenever its dependencies change. Return null to skip the query:
<script lang="ts">import { db } from '$lib/db';const auth = db.useAuth();// Only query when we have a logged-in userconst query = db.useQuery(() =>auth.user ? { todos: {} } : null);</script>{#if auth.isLoading}<p>Loading...</p>{:else if !auth.user}<p>Please log in.</p>{:else if query.isLoading}<p>Loading todos...</p>{:else}{@const todos = query.data?.todos ?? []}<p>{todos.length} todos</p>{/if}
This also works for reactive parameters. Any $state variable read inside the function will automatically trigger a new query when it changes:
<script lang="ts">import { db } from '$lib/db';let filter = $state<'all' | 'active' | 'done'>('all');const query = db.useQuery(() => {if (filter === 'all') return { todos: {} };return { todos: { $: { where: { done: filter === 'done' } } } };});</script><button onclick={() => filter = 'all'}>All</button><button onclick={() => filter = 'active'}>Active</button><button onclick={() => filter = 'done'}>Done</button>
#Writing data
Transactions in Svelte work the same way they do in React via db.transact:
<script lang="ts">import { id } from '@instantdb/svelte';import { db } from '$lib/db';function addTodo(text: string) {db.transact(db.tx.todos[id()].update({ text, done: false, createdAt: Date.now() }));}function toggleDone(todo: { id: string; done: boolean }) {db.transact(db.tx.todos[todo.id].update({ done: !todo.done }));}function deleteTodo(todoId: string) {db.transact(db.tx.todos[todoId].delete());}</script>
To learn more see our writing data docs.
#Auth
The Svelte SDK supports all of Instant's auth methods: magic codes, guest auth, Google OAuth, and more.
#useAuth
Use db.useAuth() to get the current auth state. This gives you full control over loading, error, and user states:
<script lang="ts">import { db } from '$lib/db';const auth = db.useAuth();</script>{#if auth.isLoading}<div>Loading...</div>{:else if auth.error}<div>Error: {auth.error.message}</div>{:else if auth.user}<div><p>Hello, {auth.user.isGuest ? 'Guest' : auth.user.email}!</p><button onclick={() => db.auth.signOut()}>Sign out</button></div>{:else}<div><p>Please log in.</p><button onclick={() => db.auth.signInAsGuest()}>Try as guest</button></div>{/if}
#SignedIn / SignedOut
For simpler cases where you just need to gate content on auth state, you can use the SignedIn and SignedOut guard components instead:
<script lang="ts">import { SignedIn, SignedOut } from '@instantdb/svelte';import { db } from '$lib/db';</script><SignedIn {db}><p>You are logged in!</p><button onclick={() => db.auth.signOut()}>Sign out</button></SignedIn><SignedOut {db}><p>Please log in.</p></SignedOut>
useAuth is better when you need access to isLoading, error, or user.isGuest. The guard components are simpler when you just need to show or hide content based on login state.
#Components
#Cursors
A multiplayer cursor component that tracks mouse positions via presence. Wrap any area where you want to show live cursors from other users:
<script lang="ts">import { Cursors } from '@instantdb/svelte';import { db } from '$lib/db';const room = db.room('main', 'my-room-id');</script><Cursors {room} userCursorColor="tomato"><div>Move your mouse around!</div></Cursors>
The Cursors component supports custom cursor rendering via a renderCursor snippet, a configurable wrapper element (as), and className/style props for styling. See the Presence, Cursors, and Activity docs for more details.