Database
Learn how Zero and Functions reach the space database, how migrations apply, and what the sf db commands do.
A space that runs code can have its own database. Zero declares
the schema in the capsule and gets typed access through ctx.db.
Functions opts in with runtime.database and receives
env.DB, a D1-shaped binding. Rows belong to the space, not the version.
They persist across publishes and do not rewind on rollback.
Reach it from code
In a Zero capsule, the declared tables are available on every handler’s context:
queries: {
todos: query(async (ctx) =>
ctx.db.todos
.withIndex("by_owner", (range) => range.eq("ownerId", ctx.auth.userId))
.order("desc")
.collect()
),
},
In a Functions worker, opt in through sf.jsonc:
{ "runtime": { "kind": "functions", "database": true } }
The worker then receives env.DB. Use prepare().bind() with .all(),
.first(), .run(), or .raw(). env.DB.exec() runs DDL (data
definition language) statements, and env.DB.batch() groups statements.
For the typed query contract, see Zero’s database.
Migrations
On every publish, Zero compares the capsule’s declared schema with the
live schema and applies the migration. Additive changes apply during
sf publish. Destructive changes require an explicit command, so a
publish cannot silently drop data:
sf db migrate --rename
sf db migrate --drop
Before you roll back across a migration, check the current schema.
Inspect, dump, and export
sf db dump --table projects --limit 100
sf db export --out ./backup.json
The export contains every declared table in a versioned JSON format. It replaces the destination file only after the complete export succeeds. For anything the other commands cannot do, open a console on the space’s own database:
sf db console
The console is phpMyAdmin. The same operations exist over HTTP
(GET /v1/spaces/{spaceId}/db, /db/dump, /db/export, /db/console,
and POST /v1/spaces/{spaceId}/db/migrate). See the
API reference.
A space with no runtime has no database, and database operations on it
return db_not_available.