svelte-idb bridges the gap between the imperative, asynchronous nature of IndexedDB and the synchronous, declarative world of Svelte 5. It provides a frictionless developer experience with fully typed schemas and automatic UI updates powered by $state runes.
$state and $derived for seamless, glitch-free reactivity.add, put, delete, clear) automatically trigger microtask-batched reactivity for optimal performance.where(index).equals(value) plus range operators like between, above, and below.bun add svelte-idb
# or
npm install svelte-idb
# or
pnpm add svelte-idb
For detailed step-by-step guides and examples, visit the documentation site.
Use createReactiveDB to define your schema, stores, and configuration. Do this in a shared file like src/lib/db.ts.
// src/lib/db.ts
import { createReactiveDB } from 'svelte-idb/svelte';
export interface Todo {
id?: number;
text: string;
done: boolean;
createdAt: number;
}
export const db = createReactiveDB({
name: 'my-app-db',
version: 1,
stores: {
todos: {
keyPath: 'id',
autoIncrement: true, // Auto-generates IDs for new records
}
}
});
Use the .liveAll(), .liveGet(), or .liveCount() methods. The .current property holds the reactive state and will automatically update whenever the underlying store changes.
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { db } from '$lib/db';
// 1. Create a live query
// This automatically fetches data and reacts to any changes
const todos = db.todos.liveAll();
let text = $state('');
async function addTodo() {
// 2. Mutate the database
// The `todos` query will automatically re-run and update the UI!
await db.todos.add({ text, done: false, createdAt: Date.now() });
text = '';
}
</script>
<div>
<input bind:value="{text}" placeholder="New todo..." />
<button onclick="{addTodo}">Add</button>
</div>
<!-- 3. Consume the reactive state -->
{#if todos.loading}
<p>Loading...</p>
{:else if todos.error}
<p>Error: {todos.error.message}</p>
{:else}
<ul>
{#each todos.current as todo (todo.id)}
<li>{todo.text}</li>
{/each}
</ul>
{/if}
For a complete interactive API reference, visit idb.svelte-apps.me/docs.
createReactiveDB(config)Creates and provisions an IndexedDB instance. Returns an object where each store is available as a property.
Configuration Options:
name (string): The database name. Must be unique per origin.version (number): The schema version. Increment this whenever you change the stores object.stores (object): Map of store names to their definitions (keyPath, autoIncrement).ssr (string | function): How to handle SSR. Defaults to 'noop'. Can be 'throw' or a custom handler function.db.storeName.*)These methods return a LiveQuery object containing reactive $state fields: current, loading, and error.
| Method | Description | Return Type |
|---|---|---|
liveAll() |
Reactively lists all records in the store. | LiveQuery<T[]> |
liveGet(key) |
Reactively fetches a single record by its primary key. | LiveQuery<T | undefined> |
liveCount() |
Reactively tracks the total number of records. | LiveQuery<number> |
db.storeName.*)All standard mutations automatically notify active LiveQueries to trigger Svelte updates. They return Promises.
| Method | Description |
|---|---|
add(value) |
Inserts a new record. Fails if the key already exists. |
put(value) |
Inserts or updates a record (upsert). |
delete(key) |
Removes a record by primary key. |
clear() |
Removes all records from the store. |
get(key) |
Fetches a single record (non-reactive). |
getAll() |
Fetches all records (non-reactive). |
where(index) |
Starts an indexed query builder (non-reactive MVP). |
db.storeName.where(indexName).*)The current query builder is a core-only MVP for secondary index reads.
| Method | Description |
|---|---|
equals(value) |
Matches records with an exact index value. |
between(a, b) |
Matches records within an inclusive range by default. |
above(value) |
Matches records strictly greater than the value. |
aboveOrEqual(value) |
Matches records greater than or equal to the value. |
below(value) |
Matches records strictly less than the value. |
belowOrEqual(value) |
Matches records less than or equal to the value. |
toArray() |
Returns all matching records. |
first() |
Returns the first matching record in index order. |
count() |
Returns the count of matching records. |
You can define secondary indexes in your schema to enable querying by properties other than the primary key.
const db = createReactiveDB({
name: 'my-app-db',
version: 1,
stores: {
users: {
keyPath: 'id',
indexes: {
byEmail: { keyPath: 'email', unique: true },
byAge: { keyPath: 'age' }
}
}
}
});
// Query using the standard async method
const adults = await db.users.getAllFromIndex('byAge', IDBKeyRange.lowerBound(18));
// Query using the query builder MVP
const adultsViaBuilder = await db.users.where('byAge').aboveOrEqual(18).toArray();
(Note: Reactive indexed queries are still planned. The current query builder is core-only.)
Because svelte-idb is designed for SvelteKit, rendering on the server (SSR) will safely "no-op" by default instead of crashing with window is not defined.
liveAll().current will cleanly return an empty array [] on the server.loading will be false during SSR so skeleton loaders aren't triggered server-side.The project uses Vitest Browser Mode with Playwright for real browser IndexedDB coverage.
bun install
bunx playwright install chromium
bun run test:browser
For continuous validation, the CI workflow runs:
bun run checkbun run packagebun run test:browseraddMany, putMany, and deleteMany.BroadcastChannel.MIT ยฉ Michael Obele