Schema and migrations

Version the schema with migrations

Create ordered SQL migration files and roll out additive changes before code depends on them.

8 minute lesson

~~~

You do not create tables by hand in production. D1 migrations record schema changes as ordered SQL files, committed with the application, so a clean database can be reproduced from history alone.

Create one:

npx wrangler d1 migrations create my-app-db create_notes
# ✅ Successfully created Migration '0001_create_notes.sql'

Wrangler writes a numbered file into the migrations directory. Put the SQL from the previous lesson inside it, then apply it locally:

npx wrangler d1 migrations apply my-app-db --local
# 🌀 Executing on local database my-app-db...
# ┌─────────────────────────┬────────┐
# │ 0001_create_notes.sql   │ ✅     │
# └─────────────────────────┴────────┘

When the code that uses the new schema is ready to deploy, apply the same files with --remote. Wrangler tracks which migrations already ran in a bookkeeping table, so each file runs exactly once and always in order. wrangler d1 migrations list my-app-db --local shows what is still pending.

Two habits keep this system honest. Never edit a migration file that has already been applied anywhere — it will not re-run, so your file and the real schema silently diverge. Write a new migration instead. And apply locally before remotely, every time.

Roll out additive changes first

Deployments are not instant. For a short window, old and new Worker versions serve traffic against the same database. Do not assume old and new Worker versions disappear at the same instant.

So prefer additive rollout: add a nullable column or a new table, deploy code that works with and without it, backfill if needed, then enforce or remove old structure later:

-- 0002_add_archived_at.sql
alter table notes add column archived_at integer;

The old Worker ignores the new column and keeps working. A destructive change in one step — renaming a column the live code still reads — takes production down for exactly the length of your deploy window, which is the worst possible timing.

SQLite also restricts some ALTER TABLE operations, so bigger reshapes become create-new-table, copy, swap. Keep each migration small and review the SQL before it touches remote.

Now create the first migration for the notes schema, apply it locally, list the migration state, then delete your local database state and rebuild it from only the committed files.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →