---
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 checks use the primary so a lagging replica can't accept a revoked session. For other reads that need the latest primary data, create a client inside the request:

```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();
});
```

With `session: 'first-primary'`, the first query reads from the primary and later queries see at least the same data. The default, `'first-unconstrained'`, allows the first query to use a replica.

For raw D1 calls or another query library, use `withSession()` to read from 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.
:::
