D1 (SQLite)
Cloudflare D1 is Void's default SQLite database. Start with a schema; Void manages the database locally and when you deploy.
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, Void uses its ID. A preview_database_id takes precedence during local development.
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 running database commands. After changing the Vite config, start it again to refresh it. Frameworks that manage their own Cloudflare runtime use .wrangler/state.
Migrations & Prototyping
Use void db push to apply schema changes locally without creating migration files. Before deploying, generate and commit migrations:
void db generate
void db migrate # apply locally
void deploy # apply remotely and deploySee Database commands for the full workflow.
Batch Operations
D1 supports batching multiple statements into a single 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
Void enables D1 read replication when it provisions a database. To enable it on an existing database, run void deploy --platform cloudflare --provision. Queries through db from void/db use a D1 session for each Void request, so reads can run near the request and later queries in that request see at least the same data. Framework handlers without Void'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. Authentication session checks start on the primary so a revoked session is not accepted because of replica lag. 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
Use 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();Void still handles binding inference and deployment regardless of which 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.