D1 Migration Workflow
Guided workflow for Cloudflare D1 database migrations using Drizzle ORM.
Standard Migration Flow
1. Generate Migration
This creates a new .sql file in drizzle/ (or your configured migrations directory).
2. Inspect the SQL (CRITICAL)
Always read the generated SQL before applying. Drizzle sometimes generates destructive migrations for simple schema changes.
Red Flag: Table Recreation
If you see this pattern, the migration will likely fail:
Cause: Changing a column's default value in Drizzle schema triggers full table recreation. The INSERT SELECT references the new column from the old table.
Fix: If you're only adding new columns (no type/constraint changes on existing columns), simplify to:
Edit the .sql file directly before applying.
3. Apply to Local
4. Apply to Remote
Always apply to BOTH local and remote before testing. Local-only migrations cause confusing "works locally, breaks in production" issues.
5. Verify
Fixing Stuck Migrations
When a migration partially applied (e.g. column was added but migration wasn't recorded), wrangler retries it and fails on the duplicate column.
Symptoms: pnpm db:migrate errors on a migration that looks like it should be done. PRAGMA table_info shows the column exists.
Diagnosis
Fix
Prevention
CREATE TABLE IF NOT EXISTS— safe to re-runALTER TABLE ADD COLUMN— SQLite has noIF NOT EXISTSvariant; check column existence first or use try/catch in application code- Always inspect generated SQL before applying (Step 2 above)
Bulk Insert Batching
D1's parameter limit causes silent failures with large multi-row INSERTs. Batch into chunks:
Why: D1 fails when rows x columns exceeds ~100-150 parameters.
Column Naming
New Project Setup
When creating a D1 database for a new project, follow this order:
- Deploy Worker first —
npm run build && npx wrangler deploy - Create D1 database —
npx wrangler d1 create project-name-db - Copy database_id to
wrangler.jsoncd1_databasesbinding - Redeploy —
npx wrangler deploy - Run migrations — apply to both local and remote


