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.
- โก๏ธ Svelte 5 Runes Native: Built from the ground up using
$stateand$derivedfor seamless, glitch-free reactivity. - ๐ก๏ธ SSR Safe By Design: Safely use it in SvelteKit SSR environments. Automatically no-ops or uses safe defaults on the server.
- ๐ Tiny & Zero-Dependency: Less than 2KB minzipped. No external libraries, just pure modern browser APIs.
- ๐ท๏ธ First-Class TypeScript: Define your schema once and enjoy type-safe queries, stores, and autocomplete everywhere.
- ๐ Automatic Live Queries: Mutations (
add,put,delete,clear) automatically trigger microtask-batched reactivity for optimal performance. - ๐๏ธ Indexed Query Builder (MVP): Query secondary indexes with
where(index).equals(value)plus range operators likebetween,above, andbelow. - ๐งฉ Dual Exports: Clean Separation between core logic and Svelte-specific reactive hooks.
bun add svelte-idb
# or
npm install svelte-idb
# or
pnpm add svelte-idbFor 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.
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 thestoresobject.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.
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> |
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). |
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().currentwill cleanly return an empty array[]on the server.loadingwill befalseduring SSR so skeleton loaders aren't triggered server-side.- Once the component mounts in the browser, the real IndexedDB connection is established and data hydrates automatically.
The project uses Vitest Browser Mode with Playwright for real browser IndexedDB coverage.
bun install
bunx playwright install chromium
bun run test:browserFor continuous validation, the CI workflow runs:
bun run checkbun run packagebun run test:browser
- Reactive Indexed Queries: Extend indexed queries into the Svelte reactive layer.
- Advanced Query Builder: Add richer operators and collection-style pipelines.
- Transactions: Multi-store atomic operations with auto-rollback.
- Bulk Operations:
addMany,putMany, anddeleteMany. - Cross-tab Sync: Automatic reactivity across different browser tabs using
BroadcastChannel. - Migration Sugar: Simplified API for adding columns or renaming stores.
MIT ยฉ Michael Obele