DbService in a service, or through React hooks in the renderer.
Schema
Each plugin defines a schema using zod that describes the shape of its data.src/main/schema.ts
default() will be undefined initially. Use .default() to set an initial value for a field when the database is first created.
Reading data
The database is a single JSON object called theroot. Each plugin’s data lives under its name, so root.app holds everything defined by the app plugin.
In the renderer, use the useDb hook to read from the database.
useDb is a subscription. When the selected value changes, the component re-renders automatically. Unrelated changes don’t trigger re-renders.
In a service, read through DbService.client:
readRoot() returns a synchronous snapshot since the entire root is held in memory. You can also subscribe to a specific field to react when it changes:
this.setup() so it cleans up on hot reload.
Writing data
In the renderer, useuseDbClient to get a client that can write to the database:
DbService.client:
update(), you mutate the root object directly, the same way you would with a regular JavaScript object. The database tracks these mutations and syncs them to other processes in the background.
Collections
Regular data fields are always held in memory across every process. Collections are for data that can grow large (like agent messages or logs) and should only be loaded into memory when needed. Define a collection in your schema withcollection(...):
src/main/schema.ts
concat to add items:
useCollection to subscribe to a collection’s data:
Blobs
Blobs store binary data (Uint8Array) like files or images. Like collections, they live on disk and are only loaded into memory when you read them.
Define a blob in your schema with blob(...):
src/main/schema.ts
set:
read:
Migrations
When you change your schema, existing databases need to be updated to match the new shape. Runningpnpm run db:generate compares your current schema to the previous version and creates a migration file that describes what changed.
migrations/ directory. Here’s an example of what one looks like after adding a new activeTabId field to the schema:
migrations/0003.ts
- add: introduces a new key, optionally with a default value.
- remove: drops an existing key.
- alter: updates the metadata of an existing key, like changing its default.
migrate function to transform the data with custom logic. Use ctx.apply to run the declared operations first, then modify the result:
migrations/0004.ts

