---
url: https://void.cloud/guide/database/d1.md
description: >-
  Use Cloudflare D1, Void's default SQLite database. Void runs it locally and
  provisions it when you deploy.
---

# D1 (SQLite)

[Cloudflare D1](https://developers.cloudflare.com/d1/) is Void's default SQLite database. Start with a [schema](../database.md#schema-definition); 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:

```sh
void db generate
void db migrate # apply locally
void deploy     # apply remotely and deploy
```

See [Database commands](../../reference/cli/database.md#database) for the full workflow.

## Batch Operations

D1 supports batching multiple statements into a single round-trip:

```ts
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](https://developers.cloudflare.com/d1/best-practices/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. For another read that needs the latest primary data, create a Drizzle client inside that request with `createDb(env.DB, { session: 'first-primary' })`:

```ts
import { users } from '@schema';
import { env } from 'cloudflare:workers';
import { defineHandler } from 'void';
import { createDb } from 'void/db';

export const GET = defineHandler(async () => {
  const primaryDb = createDb(env.DB, { session: 'first-primary' });
  return primaryDb.select().from(users).all();
});
```

The first query uses the primary; subsequent queries in that session see at least the same data. Without this option, `createDb()` uses `'first-unconstrained'`, allowing its first query to use a replica. 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

```ts
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

```ts
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.

::: warning ⚠️ 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.
:::
