The application expects a Cloudflare D1 binding named DB, and Drizzle writes migrations to drizzle/d1. The database schema lives in src/db/schema, with src/db/schema/index.ts as the Drizzle Kit entrypoint.

D1 is currently the only supported runtime database. Runtime code should use the local src/db modules rather than importing drizzle-orm/d1 directly from routes, actions, middleware, or UI code.

Hyperdrive support is planned for future releases. The project has proof tests for future Hyperdrive PostgreSQL and MySQL support, but these targets are not enabled at runtime. See Hyperdrive Proof for the current adapter proof-of-concept work on the database boundary.

Default Setup

The committed default is a single D1 binding in wrangler.jsonc:

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "00000000-0000-0000-0000-000000000000",
      "migrations_dir": "drizzle/d1",
    },
  ],
}

Runtime code always reads env.DB; keep that binding name unless you also update src/env.d.ts and every env.DB call site.

Local development uses the Astro Cloudflare adapter and Wrangler config. The npm run dev script runs astro dev, and the adapter reads wrangler.jsonc so the DB binding is available through the local Workers runtime. Wrangler's local D1 implementation is powered by Miniflare/workerd. No separate Miniflare config is required for the default flow.

Wrangler is already a project dev dependency. Install dependencies with npm install, then use the project scripts. If you want to call Wrangler directly, prefer npx wrangler ... so the repository-pinned version is used.

Local Development

Install dependencies first:

npm install

Generate migrations after schema changes:

npm run db:generate

Apply migrations to Wrangler's local D1 state:

npm run db:migrate:local

Optionally create a verified local user with the admin role after migrations:

npm run init:admin

This writes directly to D1 with Wrangler and does not require npm run dev.

Wrangler stores local D1 state under .wrangler/, which is ignored by git. The Miniflare-backed D1 SQLite files live at:

.wrangler/state/v3/d1/miniflare-D1DatabaseObject/

The application database is the hashed *.sqlite file in that directory. The metadata.sqlite files are Miniflare bookkeeping and are not the app database. Wrangler persists local D1 data between dev sessions by default. If you need a fresh local database, remove the relevant .wrangler state or reset it with SQL.

Run the app:

npm run dev

The app uses the same DB binding locally, but the data is local-only unless the binding is configured as remote.

Drizzle Studio

The project includes:

npm run db:studio

Drizzle Studio reads drizzle.config.ts. With this project's default driver: 'd1-http' config, Studio connects through Cloudflare's D1 HTTP API, not through Wrangler's local DB binding. Use this mode for a Cloudflare-hosted D1 database by setting the required values in the shell that starts Studio:

CLOUDFLARE_ACCOUNT_ID=your-account-id \
CLOUDFLARE_DATABASE_ID=your-d1-database-id \
CLOUDFLARE_D1_TOKEN=your-api-token \
npm run db:studio

Do not put CLOUDFLARE_D1_TOKEN in wrangler.jsonc or commit it. Drizzle Kit does not read the Wrangler login session for this path; it needs the explicit Cloudflare account, database, and token values.

If you run npm run db:studio without real Cloudflare values, Drizzle Kit will use the placeholder local values from drizzle.config.ts and eventually fail with a Cloudflare routing error such as Could not route to /client/v4/accounts/local/d1/database/local/query. That means Studio is trying to use remote D1 mode without real remote D1 credentials.

For local D1, Wrangler stores the Miniflare-backed database as a SQLite file under .wrangler/state/v3/d1/miniflare-D1DatabaseObject/ after local migrations or local app usage. Drizzle Studio can inspect that file as SQLite, but that is a local-dev convenience rather than a Cloudflare D1 API connection. This project does not commit a local Studio config because Studio support is optional; copy the config below only when you want this workflow.

First apply local migrations:

npm run db:migrate:local

Then create an uncommitted local config:

// drizzle.studio.local.config.ts
import { existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { defineConfig } from 'drizzle-kit';

const localD1Directory = '.wrangler/state/v3/d1/miniflare-D1DatabaseObject';

function toFileUrl(path: string) {
  return path.startsWith('file:') ? path : `file:${path}`;
}

function getLocalD1SqliteUrl() {
  const explicitPath = process.env.LOCAL_D1_SQLITE_PATH;

  if (explicitPath) {
    return toFileUrl(explicitPath);
  }

  if (!existsSync(localD1Directory)) {
    throw new Error(
      [
        `Local D1 state was not found at ${localD1Directory}.`,
        'Run npm run db:migrate:local first, or set LOCAL_D1_SQLITE_PATH.',
      ].join('\n'),
    );
  }

  const sqliteFiles = readdirSync(localD1Directory)
    .filter((file) => file.endsWith('.sqlite') && file !== 'metadata.sqlite')
    .sort();

  if (sqliteFiles.length !== 1) {
    throw new Error(
      [
        `Expected one local D1 SQLite database in ${localD1Directory}.`,
        ...sqliteFiles.map((file) => `- ${join(localD1Directory, file)}`),
        'Set LOCAL_D1_SQLITE_PATH to the database file you want Studio to open.',
      ].join('\n'),
    );
  }

  return toFileUrl(join(localD1Directory, sqliteFiles[0]));
}

export default defineConfig({
  dialect: 'sqlite',
  schema: './src/db/schema/index.ts',
  dbCredentials: {
    url: getLocalD1SqliteUrl(),
  },
});

Run Studio with that config:

npx drizzle-kit studio --config drizzle.studio.local.config.ts

If you have more than one local D1 file, point Studio at the one you want:

LOCAL_D1_SQLITE_PATH=.wrangler/state/v3/d1/miniflare-D1DatabaseObject/your-file.sqlite \
npx drizzle-kit studio --config drizzle.studio.local.config.ts

Drizzle Kit needs either better-sqlite3 or @libsql/client installed to open a raw SQLite file. This project does not require either package for normal D1 runtime behavior, so install one only if you want the local-file Studio workflow.

The Chrome extension is not required for the CLI workflow above. Drizzle's Chrome extension is an optional way to use Studio from supported browser-based database dashboards. For this project, local D1 inspection should use the CLI Studio server plus the local SQLite file, and Cloudflare-hosted D1 inspection should use the CLI Studio server plus D1 HTTP credentials.

Production Database

Create the remote D1 database once:

npx wrangler d1 create vk

Copy the returned database_id into wrangler.jsonc under the DB binding:

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "replace-with-cloudflare-d1-id",
      "migrations_dir": "drizzle/d1",
    },
  ],
}

For remote migrations, prefer Wrangler's D1 commands:

npm run db:migrate:remote

Wrangler reads the D1 binding from wrangler.jsonc and uses your Wrangler auth session or CLOUDFLARE_API_TOKEN from the shell or CI environment.

If you run a Drizzle Kit command that connects directly to Cloudflare D1 over HTTP, provide these values through your shell or CI secret store instead of a project environment file:

CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_DATABASE_ID=
CLOUDFLARE_D1_TOKEN=

Apply migrations to the remote database:

npm run db:migrate:remote

Optionally create a verified remote user with the admin role after migrations:

npm run init:admin -- --remote

This writes directly to the remote D1 database with Wrangler and does not require the app server to be running.

Alternate Development Databases

You have three common options when DB should point somewhere other than the default local D1 state.

Separate Local D1 State

Keep binding as DB, keep database_name as the database that Wrangler commands should target, and add a preview_database_id to isolate the local dev database identity:

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "vk",
      "database_id": "replace-with-production-cloudflare-d1-id",
      "preview_database_id": "vk-local-jane",
      "migrations_dir": "drizzle/d1",
    },
  ],
}

Then apply migrations to that local database:

npm run db:migrate:local

Use a stable preview_database_id if you want local data to persist across sessions. Use a different preview_database_id when you intentionally want a separate local D1 database.

Cloudflare-Hosted Dev Database

Create a second Cloudflare D1 database for development:

npx wrangler d1 create vk-dev

Use a named Wrangler environment so production and development bindings stay separate. Binding configuration is environment-specific, so repeat both vars and d1_databases inside the named environment:

{
  "env": {
    "development": {
      "name": "vk-development",
      "vars": {
        "EMAIL_PROVIDER": "console",
      },
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "vk-dev",
          "database_id": "replace-with-dev-cloudflare-d1-id",
          "migrations_dir": "drizzle/d1",
          "remote": true,
        },
      ],
    },
  },
}

Run local dev against that Cloudflare-hosted database by selecting the environment for the Cloudflare Vite runtime:

CLOUDFLARE_ENV=development npm run dev

Apply migrations to that remote dev database with:

npx wrangler d1 migrations apply vk-dev --remote --env development

remote: true means local requests can mutate the Cloudflare D1 database. Use a dev database, not production, unless that is intentional.

Production-Like Named Environment

For production, use the same environment pattern but point DB at the production database and omit remote: true; deploy commands run on Cloudflare and use the bound remote database there:

{
  "env": {
    "production": {
      "name": "vk-production",
      "vars": {
        "EMAIL_PROVIDER": "console",
      },
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "vk",
          "database_id": "replace-with-production-cloudflare-d1-id",
          "migrations_dir": "drizzle/d1",
        },
      ],
    },
  },
}

Apply migrations and deploy with the same environment name:

npx wrangler d1 migrations apply vk --remote --env production
npx wrangler deploy --env production

Runtime Boundary

Runtime code should continue to use the local src/db modules. Do not import drizzle-orm/d1 from app routes, actions, middleware, or UI code. Hyperdrive is a planned adapter target, but D1 is the only supported runtime database in this milestone.

The app_settings table is part of this app-owned D1 schema. It stores small key/value settings such as site.title through the local app settings action surface. It is not a database administration table and should not be used for migrations, connection management, or database target selection.