D1 (SQLite)
Cloudflare D1 is the SDK's default SQLite database. Start with a schema; the SDK manages the database locally and when you deploy.
On this pageUse D1 in local development · Deploy D1 with migrations · Know D1's limits
Local database
The dev server and local void db commands share the same database. If cloudflare.d1_databases in void.config.ts declares the D1 binding, the SDK uses its ID. During local development, a preview_database_id wins.
To store local data outside .void, set voidPlugin({ persistTo: 'local-state' }) in your Vite config. Start the dev server once to record that directory before you run database commands. Restart it after any Vite config change. Frameworks that manage their own Cloudflare runtime use .wrangler/state.
Migrations & prototyping
void db push applies schema changes locally without migration files. This step generates and applies migrations before you deploy. Commit them.
void db generate
void db migrate # apply locallyExample output: A new migration file, then the migration applied to the local database.
void deploy # apply remotely and deployExample output: Pending migrations applied remotely, then the deployed URL.
See Database commands for the full workflow.
Batch operations
This step batches several statements into one round-trip.
import { users, posts } from '@schema';
import { db, eq } from 'void/db';
await db.batch([
db.delete(posts).where(eq(posts.userId, userId)),
db.delete(users).where(eq(users.id, userId)),
]);Read replication
The SDK enables D1 read replication when it provisions a database. For an existing database, run void deploy --platform cloudflare --provision. Queries through db from void/db use a D1 session for each SDK request, so reads can run near the request and later queries in that request see at least the same data. Framework handlers without the SDK's request scope start each database operation on the primary. createDb starts a replica-capable session for the binding you pass it.
Replicas can lag between requests. Session checks start on the primary, so replica lag never accepts a revoked session. If another read must start with the latest primary data, use env.DB.withSession('first-primary') from cloudflare:workers. Other query libraries and raw D1 calls need their own withSession() to use replicas. A bare D1 binding reads from the primary.
Alternative query layers
This step uses env.DB from cloudflare:workers with another query library or the raw D1 API.
Kysely
import { env } from 'cloudflare:workers';
import { Kysely } from 'kysely';
import { D1Dialect } from 'kysely-d1';
const db = new Kysely<Database>({ dialect: new D1Dialect({ database: env.DB }) });Raw D1
import { env } from 'cloudflare:workers';
const { results } = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).all();The SDK still handles binding inference and deployment, whatever query layer you use.
⚠️ Meta framework limitation
Nuxt and SvelteKit 2 do not support cloudflare:workers imports during local development. Use void/db or your framework's binding access API. SvelteKit 3 supports these imports.