D1
D1 is SQLite on env.<binding> — the same Worker API as Cloudflare D1 (within celld gaps).
celld runs queries and stores the database in the version fleet bucket. cellp imports or branches D1 when a version starts and exposes SQL operator APIs.
Bindings overview · Binding guides · Platform data
1. Declare it
In wrangler.jsonc:
"d1_databases": [
{
"binding": "DB",
"database_name": "commerce",
"database_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
]binding → env.DB. Keep database_id unchanged on child versions — cellp inherits the parent id on fork, and a mismatched id breaks D1 branch (LTX scope). One d1_databases entry per bundle is the supported shape. Full file: Configure bindings.
2. Use it in the Worker
export default {
async fetch(request, env) {
await env.DB.exec(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL
);
`)
if (request.method === 'POST') {
const { name, price_cents } = await request.json()
await env.DB.prepare(
'insert into products (name, price_cents) values (?, ?)',
)
.bind(name, price_cents)
.run()
return new Response(null, { status: 201 })
}
const { results } = await env.DB.prepare(
'select id, name, price_cents from products order by id',
).all()
return Response.json({ products: results })
},
}Also useful: .first(), .first('column'), db.batch([...]) as celld implements them.
3. Put rows in (not via wrangler)
Wrangler only names the database. Data comes from:
| Method | When |
|---|---|
seed.db in the artifact | Root version import (CI or local seed scripts) |
| SQL in the Worker | Schema + small fixtures (CREATE TABLE IF NOT EXISTS) |
| Dashboard → Storage → D1 | Browse tables, run SQL on a ready version |
| Child version | Automatic branch from parent_version_id (celld d1 branch into the child bucket) |
| Root + offshoot export | Platform may seed from an offshoot export on first deploy—see Platform data |
Step-by-step: Platform data.
Preview vs production
| Version kind | D1 data |
|---|---|
| Root (no parent) | Empty unless you seed.db, SQL in the Worker, or Dashboard |
Child (parent_version_id) | Branch — parent snapshot at fork + writes in this version’s bucket |
| Preview Host | Reads/writes this version’s fork only |
| Production Host | Unchanged until you promote the version that should serve prod |
Promote switches which version’s bucket is production; it does not merge rows written on the old prod line after the fork.
Operator API (optional)
Same as the Dashboard, for scripts. Prefix:
/v1/projects/{project}/versions/{version}/
| Call | Use |
|---|---|
GET …/database | Summary |
GET …/database/tables | Table list |
GET …/database/tables/{name}/rows | Browse (limit, offset) |
POST …/database/query | { "sql": "…" } |
503 → that version’s celld is down (archived or still deploying). Requires admin token — see API.
celld vs Cloudflare
- Result sets cap at 100,000 rows or 32 MiB per query.
- Invalid UTF-8 in SQLite
TEXTis rejected — useBLOBfor arbitrary bytes.
More: celld cloudflare-compat — D1.
Not included
Cloudflare D1 Time Travel, sharing one SQLite file across two ready versions, posting SQLite bytes in JSON.