Running Migrations
Running migrations is the process of applying your schema changes to an actual database. Whether you’re testing locally or deploying to production, understanding how to run migrations confidently is essential.
Local Migrations
Local migrations run against a D1 database stored on your machine. This is your development sandbox—experiment freely without affecting anyone else.
Applying Local Migrations
Use the wrangler d1 migrations apply command with the --local flag:
pnpm wrangler d1 migrations apply DB --localExpected output when migrations are applied:
Migrations to be applied:┌─────────────────────────────────────┐│ name │├─────────────────────────────────────┤│ 0000_initial.sql │├─────────────────────────────────────┤│ 0001_add_user_profile.sql │└─────────────────────────────────────┘
✔ About to apply 2 migration(s)Your database may not be available to serve requests during the migration, continue? … yes🌀 Executing on local database DB from .wrangler/state/v3/d1:🌀 To execute on your remote database, add a --remote flag to your wrangler command.
Applying migration: 0000_initial.sql Running query 1/2: CREATE TABLE `users`... ✔ Query 1/2 succeeded Running query 2/2: CREATE TABLE `posts`... ✔ Query 2/2 succeeded✔ Applied 0000_initial.sql
Applying migration: 0001_add_user_profile.sql Running query 1/1: ALTER TABLE `users`... ✔ Query 1/1 succeeded✔ Applied 0001_add_user_profile.sql
✅ 2 migration(s) applied successfullyExpected output when no migrations are pending:
Migrations to be applied:┌──────┐│ name │├──────┤│ None │└──────┘
✔ No migrations to applyThe Local Database Location
When you run local migrations, Wrangler stores the SQLite database in your project:
.wrangler/└── state/ └── v3/ └── d1/ └── miniflare-D1DatabaseObject/ └── <database-id>.sqliteThis file is automatically created and managed. It’s listed in .gitignore by default—never commit it.
Local Development Workflow
Here’s a typical local development cycle:
flowchart LR A["Edit schema.ts"] --> B["Generate migration"] B --> C["Apply locally"] C --> D["Test your app"] D --> E{Works?} E -->|No| A E -->|Yes| F["Commit changes"]- Edit your schema: Modify
src/db/schema.ts - Generate: Run
pnpm drizzle-kit generate - Apply: Run
pnpm wrangler d1 migrations apply DB --local - Test: Start your dev server and verify the changes
- Repeat: If something’s wrong, adjust and regenerate
Remote Migrations
Remote migrations apply changes to your actual Cloudflare D1 databases—staging, production, or any other environment you’ve configured.
Understanding Environments
Your wrangler.toml can define multiple environments:
name = "my-app"main = "src/index.ts"
# Default environment (production)[[d1_databases]]binding = "DB"database_name = "my-app-db"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Staging environment[env.staging][[env.staging.d1_databases]]binding = "DB"database_name = "my-app-db-staging"database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"Applying Migrations to Staging
Always test on staging before production:
pnpm wrangler d1 migrations apply DB --remote --env stagingExpected output:
Migrations to be applied:┌─────────────────────────────────────┐│ name │├─────────────────────────────────────┤│ 0002_add_comments.sql │└─────────────────────────────────────┘
✔ About to apply 1 migration(s)Your database may not be available to serve requests during the migration, continue? … yes🌀 Executing on remote database DB (yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy):
Applying migration: 0002_add_comments.sql Running query 1/1: CREATE TABLE `comments`... ✔ Query 1/1 succeeded✔ Applied 0002_add_comments.sql
✅ 1 migration(s) applied successfullyApplying Migrations to Production
Once verified on staging, apply to production:
pnpm wrangler d1 migrations apply DB --remoteWithout --env, Wrangler uses the default environment (production).
Expected output:
Migrations to be applied:┌─────────────────────────────────────┐│ name │├─────────────────────────────────────┤│ 0002_add_comments.sql │└─────────────────────────────────────┘
✔ About to apply 1 migration(s)Your database may not be available to serve requests during the migration, continue? … yes🌀 Executing on remote database DB (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx):
Applying migration: 0002_add_comments.sql Running query 1/1: CREATE TABLE `comments`... ✔ Query 1/1 succeeded✔ Applied 0002_add_comments.sql
✅ 1 migration(s) applied successfullySkipping Confirmation Prompts
For CI/CD pipelines, use the --yes flag to skip confirmation:
pnpm wrangler d1 migrations apply DB --remote --yesThis is useful in automated deployments where no one is there to type “yes”.
Deployment Order
flowchart TD
A["Commit migrations to Git"] --> B["CI/CD triggered"]
B --> C["Apply to staging"]
C --> D["Run integration tests"]
D --> E{Tests pass?}
E -->|No| F["Fix and recommit"]
E -->|Yes| G["Apply to production"]
G --> H["Verify deployment"]
Key principles:
- Local first: Always test migrations locally before committing
- Staging second: Apply to staging and run tests
- Production last: Only after staging is verified
- Same migrations everywhere: Never modify migrations between environments
Checking Migration Status
Before running migrations, check what’s pending. This helps you understand the current state of your database.
Listing Local Migrations
pnpm wrangler d1 migrations list DB --localExpected output:
Migrations:┌──────────────────────────────┬────────────────────┐│ name │ status │├──────────────────────────────┼────────────────────┤│ 0000_initial.sql │ ✅ Applied │├──────────────────────────────┼────────────────────┤│ 0001_add_user_profile.sql │ ✅ Applied │├──────────────────────────────┼────────────────────┤│ 0002_add_comments.sql │ ⏳ Pending │└──────────────────────────────┴────────────────────┘Listing Remote Migrations
Check staging or production status:
# Stagingpnpm wrangler d1 migrations list DB --remote --env staging
# Productionpnpm wrangler d1 migrations list DB --remoteExpected output (production with all applied):
Migrations:┌──────────────────────────────┬────────────────────┐│ name │ status │├──────────────────────────────┼────────────────────┤│ 0000_initial.sql │ ✅ Applied │├──────────────────────────────┼────────────────────┤│ 0001_add_user_profile.sql │ ✅ Applied │├──────────────────────────────┼────────────────────┤│ 0002_add_comments.sql │ ✅ Applied │└──────────────────────────────┴────────────────────┘Comparing Environments
A useful practice is to compare migration status across environments:
# Check all environments at onceecho "=== Local ===" && pnpm wrangler d1 migrations list DB --localecho "=== Staging ===" && pnpm wrangler d1 migrations list DB --remote --env stagingecho "=== Production ===" && pnpm wrangler d1 migrations list DB --remoteThis helps identify if migrations are out of sync between environments.
Troubleshooting Common Errors
Even with careful planning, migration issues can occur. Here are the most common problems and their solutions.
1. “No migrations folder found”
Error message:
Error: No migrations folder found at ./migrationsCause: Wrangler can’t find the migrations directory.
Solutions:
-
Ensure migrations have been generated:
Terminal window pnpm drizzle-kit generate -
Verify the migrations directory exists and contains
.sqlfiles:Terminal window ls -la migrations/ -
Check your
wrangler.tomlpoints to the correct path:[[d1_databases]]binding = "DB"database_name = "my-db"database_id = "..."migrations_dir = "migrations" # Ensure this matches your folder
2. “Migration already applied”
Error message:
Error: Migration 0000_initial.sql has already been appliedCause: You’re trying to apply a migration that’s already in the database.
Solutions:
-
This is usually not an error—check your migration status:
Terminal window pnpm wrangler d1 migrations list DB --local -
If you see all migrations as applied, you’re up to date. Nothing more to do.
-
If you modified a migration file after applying it (don’t do this!), you’ll need to either:
- Reset your local database (delete
.wrangler/state/v3/d1/) - Create a new corrective migration
- Reset your local database (delete
3. SQL Syntax Errors
Error message:
Error: near "ADDING": syntax error at migrations/0002_add_column.sql:1Cause: Invalid SQL syntax in the migration file.
Solutions:
-
Review the SQL file for typos:
Terminal window cat migrations/0002_add_column.sql -
Common D1/SQLite syntax issues:
- Use
integernotINTfor auto-increment primary keys - Use backticks (`) for identifiers, not double quotes
- No
ENUMtype—usetextwithCHECKconstraints - No
BOOLEAN—useinteger(0 or 1)
- Use
-
Test your SQL directly:
Terminal window pnpm wrangler d1 execute DB --local --command "SELECT 1"
4. “Database not found”
Error message:
Error: Database DB not found. Please check your wrangler.toml configuration.Cause: The D1 database binding or ID is misconfigured.
Solutions:
-
Verify your
wrangler.tomlhas the correct binding name:[[d1_databases]]binding = "DB" # This must match the commanddatabase_name = "my-db"database_id = "..." -
For remote databases, ensure the database exists:
Terminal window pnpm wrangler d1 list -
If the database doesn’t exist, create it:
Terminal window pnpm wrangler d1 create my-db
5. “Foreign key constraint failed”
Error message:
Error: FOREIGN KEY constraint failed at migrations/0003_add_orders.sql:15Cause: The migration references a table or column that doesn’t exist yet.
Solutions:
-
Check migration order—foreign key tables must be created after referenced tables
-
Ensure the referenced table and column exist:
Terminal window pnpm wrangler d1 execute DB --local --command ".schema users" -
If migrations are out of order, you may need to:
- Rename migration files to fix the sequence
- Regenerate migrations with proper dependencies
6. “Table already exists”
Error message:
Error: table users already exists at migrations/0000_initial.sql:1Cause: The migration is trying to create a table that already exists.
Solutions:
-
For local development, reset the database:
Terminal window rm -rf .wrangler/state/v3/d1pnpm wrangler d1 migrations apply DB --local -
Use
CREATE TABLE IF NOT EXISTSfor idempotent migrations (though this can hide issues) -
Check if migrations were partially applied—you may need to manually fix the database state
7. “Permission denied” on Remote
Error message:
Error: You do not have permission to access this databaseCause: Authentication or authorization issues with Cloudflare.
Solutions:
-
Re-authenticate with Cloudflare:
Terminal window pnpm wrangler login -
Verify you have access to the account:
Terminal window pnpm wrangler whoami -
Check that the database belongs to your account:
Terminal window pnpm wrangler d1 list
8. Migration Timeout
Error message:
Error: Migration timed out after 30 secondsCause: A large migration or slow network connection.
Solutions:
- Break large migrations into smaller pieces
- For data migrations with many rows, process in batches
- Check your network connection for remote migrations
- Try again—transient network issues are common
Quick Reference
| Task | Command |
|---|---|
| Apply migrations locally | pnpm wrangler d1 migrations apply DB --local |
| Apply to staging | pnpm wrangler d1 migrations apply DB --remote --env staging |
| Apply to production | pnpm wrangler d1 migrations apply DB --remote |
| Apply without confirmation | pnpm wrangler d1 migrations apply DB --remote --yes |
| List local migrations | pnpm wrangler d1 migrations list DB --local |
| List remote migrations | pnpm wrangler d1 migrations list DB --remote |
| Check migration status (staging) | pnpm wrangler d1 migrations list DB --remote --env staging |
Summary
Running migrations is straightforward once you understand the commands:
- Local migrations use
--localand affect only your development database - Remote migrations use
--remoteand affect actual Cloudflare D1 databases - Environment flags (
--env staging) target specific environments - Status checks with
migrations listshow what’s applied and pending - Troubleshooting usually involves checking syntax, configuration, or database state
With these tools at your disposal, you can confidently evolve your database schema across all environments.