Prisma Next is in early access.Read the docs

Rollbacks and recovery

Rolling back is planning one more migration to a state you've already been in. Recovery is fixing the cause and re-running, safely.

Two different situations get called "rollback", and Prisma Next treats them differently:

  • The migration applied, but the change was wrong. You need to move the database back to an earlier state. That's a rollback, and in Prisma Next it's one more migration.
  • The migration failed partway. You need to get unstuck. That's recovery, and it usually means fixing the cause and re-running, because re-running is safe.

Rollback: a migration like any other

There is no migrate down command, and no separate "down migration" files. In the graph model, the state you want to return to is a node you've already visited, so rolling back means planning a new edge that points at it. If you know git, this is git revert, not git reset: history only ever grows, and the ledger (the applied-history record every database keeps) retains the full round trip.

A rollback is a new migration, not rewritten historyStep 1 of 3
add_display_namerollback (a new migration)705b1a6before the changee6b5c28after add_display_name
add_display_name applies cleanly and the database sits at the new state. Then the team decides the change was wrong.

Suppose 20260707T1008_add_display_name shipped and needs to come back out. Plan the reverse edge; <dir>^ means "the state before that migration":

npx prisma-next migration plan \
  --from 20260707T1008_add_display_name \
  --to 20260707T1008_add_display_name^ \
  --name rollback_display_name
✔ Planned 1 operation(s)


└─ Drop column "displayName" from "user" (destructive)

⚠ This migration contains destructive operations that may cause data loss.

from:   sha256:e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63
to:     sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5

DDL preview

ALTER TABLE "public"."user" DROP COLUMN "displayName";

The planner diffs the two contract states and writes the operations that undo the change, flagged destructive because they are. This is a real migration: review it, edit it (for example, to archive the column's data into another table before the DROP), commit it. Then apply it like any other migration:

npx prisma-next migrate --to 20260707T1008_add_display_name^

A rollback also leaves a cycle in the graph, so the next migration plan cannot pick its starting point automatically. It fails with MIGRATION.NO_TARGET and lists the reachable states; plan with an explicit --from <state> until history moves forward again.

One loose end remains after the database moves back: your contract source still contains the change, so prisma-next db verify reports a hash mismatch until you revert the schema and re-run prisma-next contract emit. Roll back the contract in the same commit as the rollback migration and the two stay in step.

Afterwards the graph shows the round trip: a forward edge up, a rollback edge back down:

*   e6b5c28  @contract
|^  20260707T1008_add_display_name       705b1a6 -> e6b5c28  3 ops
|v  20260707T1010_rollback_display_name  e6b5c28 -> 705b1a6  1 ops
*   705b1a6
|^  20260707T1005_init                         - -> 705b1a6  2 ops
*   -

The database's marker (its record of which graph node it currently matches) is back at 705b1a6, and the ledger records both the apply and the rollback. Nothing was rewritten or deleted.

Two things to be clear-eyed about:

  • A rollback does not resurrect data. Dropping the column discards whatever the forward migration and the app wrote into it. If that data matters, edit the rollback migration to save it somewhere first; that's exactly why the rollback is an editable migration and not an automatic mechanism.
  • You don't have to retrace every step. An edge can jump from the current state directly to any earlier node, skipping intermediate states: one planned migration, one apply, even if you're rolling back three changes.

One planning caveat after a rollback

A rollback edge creates a cycle in the graph (A → B → A), and with a cycle the planner can no longer infer "the latest state" on its own. The next time you run migration plan, pass --from explicitly (a migration directory name or hash); the error you'd otherwise get, MIGRATION.NO_TARGET, says exactly this.

Recovery: when a migration fails partway

A failed migrate run stops at the failing operation and reports it precisely:

✖ Operation alterNullability.setNotNull.user.nickname failed during precheck:
  ensure no NULL values in "nickname" (PN-RUN-3000)
  Why: Migration runner failed
  Fix: Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.

The playbook:

  1. Read which check failed. The error names the operation and the specific precheck or postcheck, in plain language. Here, rows with NULL still exist, so tightening the constraint would fail.
  2. Nothing to clean up. On PostgreSQL the whole run was one transaction, so the failure rolled it back completely: the database is exactly where it was before the run, and migrations applied in earlier runs are untouched. There is no "half-applied migration" to untangle by hand.
  3. Fix the cause, then re-run prisma-next migrate. Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself; here you'd edit the migration to add a backfill before the setNotNull, recompile with node migration.ts, and apply again. Re-running is safe, for the reasons covered in the failure model.

The precheck is also what protects you against the works-in-dev-breaks-in-prod trap. Your dev database had no NULLs, production does; the precheck halts production before the destructive ALTER touches anything, with an error pointing at the exact rows-with-NULLs condition, not a generic constraint violation mid-statement.

Drift: when the database isn't where migrations left it

If someone changed the database outside of migrations (a hand-run ALTER, a restore from an old backup), the marker or the live schema won't match any state the graph knows, and migrate refuses to run rather than pile changes onto drift. Your options, in order of preference:

  • prisma-next db verify checks whether the database still matches your contract, and fails with a precise error when it doesn't.
  • In development, prisma-next db update reconciles the database directly to your contract without walking the graph: quick, but off the record, so treat it as a dev-only reset.
  • For a database with no history at all (a fresh environment, or adopting Prisma Next on an existing schema), prisma-next db init bootstraps it to the current contract and signs the marker.

Prompt your coding agent

Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent. Ask your agent to:

  • "Plan a rollback for the last migration and show me its destructive operations before I decide."
  • "This migrate run failed. Read the error, fix the migration, and re-run it."
  • "Check whether staging has drifted from the contract and explain the differences."

See also

On this page