# Rollbacks and recovery (/docs/orm/migrations/rollbacks-and-recovery)

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

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

Location: ORM > Migrations > Rollbacks and recovery

Two different situations get called "rollback", and Prisma 8 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 8 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 [#rollback-a-migration-like-any-other]

There is no `migrate down` command, and no separate "down migration" files. In the [graph model](https://www.prisma.io/docs/orm/migrations/the-migration-graph), 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.

<ConceptAnimation name="migration-rollback" />

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

  

#### bun

```bash
bunx prisma@latest migration plan \
  --from 20260707T1008_add_display_name \
  --to 20260707T1008_add_display_name^ \
  --name rollback_display_name
```

#### pnpm

```bash
pnpm dlx prisma@latest migration plan \
  --from 20260707T1008_add_display_name \
  --to 20260707T1008_add_display_name^ \
  --name rollback_display_name
```

#### yarn

```bash
yarn dlx prisma@latest migration plan \
  --from 20260707T1008_add_display_name \
  --to 20260707T1008_add_display_name^ \
  --name rollback_display_name
```

#### npm

```bash
npx prisma@latest migration plan \
  --from 20260707T1008_add_display_name \
  --to 20260707T1008_add_display_name^ \
  --name rollback_display_name
```

```text
✔ 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:

  

#### bun

```bash
bunx prisma@latest db migrate --to 20260707T1008_add_display_name^
```

#### pnpm

```bash
pnpm dlx prisma@latest db migrate --to 20260707T1008_add_display_name^
```

#### yarn

```bash
yarn dlx prisma@latest db migrate --to 20260707T1008_add_display_name^
```

#### npm

```bash
npx prisma@latest db 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 `db verify` reports a hash mismatch until you revert the schema and re-run `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:

```text
*   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 rather than 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 [#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 [#recovery-when-a-migration-fails-partway]

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

```text
✖ 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-cli migrate --to <contract>` — previously applied migrations are preserved.
```

The hint is the CLI's literal output: `prisma-cli migrate` is its internal name for `db migrate`, so the command to re-run is `npx prisma@latest db migrate --to <contract>`.

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 `db migrate`.** Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself; here you'd [edit the migration](https://www.prisma.io/docs/orm/migrations/editing-a-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](https://www.prisma.io/docs/orm/migrations/applying-a-migration#when-something-goes-wrong).

The precheck also protects you when a migration works in development but would fail in production. Your dev database had no `NULL`s, but production does. The precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition instead of a generic constraint violation mid-statement.

### Drift: when the database isn't where migrations left it [#drift-when-the-database-isnt-where-migrations-left-it]

If someone changed the database outside of migrations (a hand-run `ALTER`, a restore from an old backup), two things can happen. When the change also moved the marker to a state the graph doesn't know, `db migrate` fails before running any SQL rather than pile changes onto drift. When the marker is intact and only the live schema changed, `db migrate` still runs: each operation's postcheck decides whether the work is already done, so a hand-applied change it can recognise is skipped, and one it can't fails that operation. Either way, start by finding out where the database really is. Your options, in order of preference:

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

> [!NOTE]
> What's early
> 
> Reverse planning (`--to <dir>^`), destructive-operation warnings, resumable re-runs, and the ledger all work today, as shown above. Some gaps remain. There's no rehearsal mode that executes a migration against a shadow copy first. Recovery from drift beyond the `db verify`/`db update`/`db init` trio is manual. The planning caveat after cycles (explicit `--from`) is a real papercut we expect to smooth out. For deep or unusual situations, `#prisma-next` on [Discord](https://pris.ly/discord) is the fastest route.

## Prompt your coding agent [#prompt-your-coding-agent]

Projects scaffolded with `create-prisma@latest` install [Prisma 8 skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) 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 db 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 [#see-also]

* [Applying a migration](https://www.prisma.io/docs/orm/migrations/applying-a-migration): the failure model in the apply flow
* [Editing a migration](https://www.prisma.io/docs/orm/migrations/editing-a-migration): fixing a migration that failed for a data reason
* [The migration graph](https://www.prisma.io/docs/orm/migrations/the-migration-graph): why backwards is just another edge

## Related pages

- [`Applying a migration`](https://www.prisma.io/docs/orm/migrations/applying-a-migration): The db migrate command walks the graph from where your database is to where you want it, with a preview, a checkpoint at every step, and safe retries.
- [`Editing a migration`](https://www.prisma.io/docs/orm/migrations/editing-a-migration): A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL, then recompile it with one command.
- [`Generating a migration`](https://www.prisma.io/docs/orm/migrations/generating-a-migration): Turn a contract change into a reviewable migration with the migration plan command.
- [`How migrations work`](https://www.prisma.io/docs/orm/migrations/how-migrations-work): Change your contract, plan a migration, review it, apply it. Every step is checked before and after it runs.
- [`The migration graph`](https://www.prisma.io/docs/orm/migrations/the-migration-graph): You changed your schema, a teammate changed theirs, both merged. The migration graph is what lets every database catch up without renaming files or rebuilding history.