Appearance
Schema rollback
Schema changes apply through committed Prisma migrations (pnpm run prisma-migrate-deploy — see Database). migrate deploy is forward-only: it never reverses an applied migration. There is no migrate down. Rolling back is therefore an act of rolling forward — you author a new migration that undoes the change — plus, when a deploy fails partway, a manual recovery step.
1. Roll back by authoring a new (contract/down) migration
To undo a migration that has already been applied to a shared/production database:
Never edit the applied migration's SQL. Prisma records each migration's checksum; editing applied SQL makes
migrate deployfail on a checksum mismatch.Revert the change in
schema.prisma(e.g. restore the dropped column, remove the new model).Author a fresh migration capturing that reversal:
bashpnpm run prisma-migrate-dev # creates migrations/<timestamp>_revert_<name>/migration.sqlReview the generated SQL (this is the "down"/contract step), commit it, and ship it the normal way —
pnpm run prisma-migrate-deployapplies it in order on every environment.
Reversing a destructive change (a DROP) cannot recover dropped data — the forward migration already deleted it. The reversal only restores the shape; backfill the data from a backup if needed (see Backup & retention).
2. Recover a half-applied (failed) migration
If migrate deploy fails midway, Prisma marks that migration failed in _prisma_migrations and refuses to apply anything further until you resolve it. You have two recovery routes:
The migration did NOT change the DB (or you manually reverted its partial effects) — mark it rolled back so Prisma forgets it, fix the migration SQL, then redeploy:
bash# wraps `prisma migrate resolve --rolled-back <migration_name>` pnpm run prisma-migrate-resolve --rolled-back <migration_name> pnpm run prisma-migrate-deployThe migration's effect is actually fully in place (e.g. it failed on a late, idempotent statement) — mark it applied instead:
bashpnpm run prisma-migrate-resolve --applied <migration_name>
<migration_name> is the migration directory name (e.g. 20260628120000_add_widget_table). Always take a snapshot before intervening (see Backup & retention).
3. Destructive changes: expand → migrate → contract
Never drop or rename in a single migration on a live DB. Split it across releases so each migration is independently safe and each can be rolled back by rolling forward:
- Expand — add the new column/table/shape (nullable or defaulted). Backward-compatible; old code keeps working.
- Migrate — backfill data and switch every reader/writer to the new shape. Deploy and verify.
- Contract — only once nothing references the old shape, author the migration that drops it.
The contract step is the only destructive one, and it is gated: the diff must pass tools/db/schema-destructive-guard.mjs (run via pnpm run prisma-schema-check), which rejectsDROP TABLE/DROP COLUMN/DROP CONSTRAINT and ADD COLUMN … NOT NULL without a DEFAULT unless explicitly acknowledged with ALLOW_DESTRUCTIVE_SCHEMA=true. CI runs the same guard against origin/main on every PR. See the Schema-Change Workflow for the full sequence.
Take a database snapshot immediately before any contract (destructive) deploy. That snapshot is the only path back to dropped data — the forward "rollback" migration restores structure, not rows.
Verified against v1.0.0