Workers D1 -- Schema Migrations and Backups
In this tutorial, you will learn how to evolve your Cloudflare Workers D1 database schema over time using Wrangler Migration files and how to back up and restore your data safely. Schema migrations are important because they let you alter table structures without losing existing data or breaking running Workers. A real-world example is a blog platform adding a tags column to its posts table while thousands of concurrent readers continue to query the database.
Why Migrations Matter
In production, you cannot simply drop and recreate tables every time your data model changes. D1 migrations allow you to apply incremental schema changes -- adding columns, creating indexes, or seeding data -- in a controlled, repeatable way. Each Migration is a numbered SQL file that Wrangler applies in order. If multiple developers work on the same project, migrations prevent schema drift and ensure every environment is identical. D1 also supports backup and restore via the Wrangler CLI, letting you export the full database to a SQL file or import one.
Migration Workflow
flowchart LR
A[Start] --> B[Create Migration file]
B --> C[npx wrangler d1 migrations create]
C --> D[Write SQL in Migration file]
D --> E[Apply Migration]
E --> F[npx wrangler d1 migrations apply]
F --> G{Check result}
G -->|Success| H[Database updated]
G -->|Failure| I[Rollback manually]
H --> J[Next Migration]
style A fill:#3498db,color:#fff
style H fill:#2ecc71,color:#fff
style I fill:#e74c3c,color:#fff
Wrangler tracks which migrations have been applied in a _d1_migrations table. Each Migration runs exactly once, in sequence, ensuring deterministic schema evolution.
Creating Your First Migration
# Generate a new migration file
npx wrangler d1 migrations create my-database add-tags-to-posts
# Expected output:
# Creating migration: migrations/0001_add-tags-to-posts.sql
# Migration file created
Wrangler creates a timestamp-prefixed SQL file in a migrations/ directory. You then edit the file to contain your schema changes.
-- migrations/0001_add-tags-to-posts.sql
ALTER TABLE posts ADD COLUMN tags TEXT NOT NULL DEFAULT '';
CREATE INDEX idx_posts_tags ON posts(tags);
The ALTER TABLE statement adds a tags column to the posts table. The DEFAULT clause ensures existing rows receive an empty string. A covering index on tags speeds up tag-based queries.
Applying Migrations
// Bind D1 to Worker and query after migration
export default {
async fetch(request, env) {
// After migration 0001, the tags column is available
const { results } = await env.DB.prepare(
'SELECT id, title, tags FROM posts WHERE tags LIKE ?1'
).bind('%cloudflare%').all();
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' }
});
}
};
// Request: GET /posts?tag=cloudflare
// Expected response:
// [{"id": 1, "title": "Getting Started", "tags": "cloudflare,workers"}, {"id": 3, "title": "D1 Deep Dive", "tags": "cloudflare,d1"}]
Apply the Migration with Wrangler:
npx wrangler d1 migrations apply my-database --remote
# Expected output:
# Applying migration 0001_add-tags-to-posts.sql...
# Migration applied successfully
The --remote flag applies to the production D1 instance. Omit it to apply to the local development database.
Backing Up and Restoring D1
# Export the full database to a SQL file
npx wrangler d1 export my-database --remote --output ./backup-2026-06-23.sql
# Expected output:
# Exporting database my-database (remote)...
# Exported 15 tables, 1024 rows to backup-2026-06-23.sql
The exported file contains CREATE TABLE and INSERT statements that recreate the full database. Use this for disaster recovery or cloning environments.
# Restore from backup
npx wrangler d1 import my-database --remote --file ./backup-2026-06-23.sql
# Expected output:
# Importing backup-2026-06-23.sql into my-database (remote)...
# Import completed: 15 tables, 1024 rows
Import drops all existing data and recreates the database from the SQL file. Use with caution in production -- always take a fresh backup before restoring.
Seeding Data via Migration
// migrations/0002_seed_categories.sql
// -- Seed initial categories for the blog
// INSERT OR IGNORE INTO categories (id, name, slug) VALUES
// (1, 'Technology', 'technology'),
// (2, 'Security', 'security'),
// (3, 'DevOps', 'devops');
export default {
async fetch(request, env) {
const { results } = await env.DB.prepare(
'SELECT name, slug FROM categories ORDER BY id'
).all();
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' }
});
}
};
// Expected response:
// [{"name": "Technology", "slug": "technology"}, {"name": "Security", "slug": "security"}, {"name": "DevOps", "slug": "devops"}]
Seed data migrations are idempotent when using INSERT OR IGNORE. The same Migration can be applied multiple times without creating duplicate rows.
Common Errors and Troubleshooting
Migration Already Applied
If you try to apply a Migration that has already run, Wrangler skips it with a message like Migration 0001 already applied, skipping. This prevents duplicate schema changes.
ALTER TABLE Limitations
D1 (backed by SQLite) supports adding columns with ALTER TABLE ... ADD COLUMN but does not support dropping or renaming columns. To remove a column, create a new table, copy data, drop the old table, and rename the new one as part of a Migration.
Failed Migration Recovery
If a Migration fails mid-way (e.g., a syntax error), Wrangler marks it as not applied. Fix the SQL file and re-run. D1 does not support automatic rollback -- manual correction is required.
Export Timeouts
Large D1 databases may time out during export. Use the --no-data flag to export only the schema if data is not needed. For full backups, schedule exports during low-traffic periods.
Backup File Size
Exported SQL files can be large. Compress backups with gzip before archiving. D1 databases up to 1 GB export to SQL files of similar size.
Practice Questions
- What Wrangler command creates a new D1 Migration file?
- How does D1 track which migrations have already been applied?
- What SQLite limitation affects schema changes in D1 migrations?
FAQ
Summary
D1 migrations provide a structured way to evolve your database schema using Wrangler-generated SQL files applied in sequence. Backups export the full database as SQL for disaster recovery. While D1 supports adding columns and creating indexes, column removal requires a manual table rebuild pattern. Combine migrations with automated backup workflows to maintain production reliability.
This guide is brought to you by the developers of Cloudflare, SQLite Database, and Durga Antivirus Pro at DodaTech.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro