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.
Consider this situation: you add a phone column to the schema on your branch, a teammate adds an avatarUrl column on theirs, and both branches merge the same afternoon. Now your laptop, your teammate's laptop, staging, and production each have a slightly different version of the schema. Something has to bring them all to the merged state, without losing work or running the same change twice.
Classic migration tools make this a complicated and time-consuming process. Migrations are kept in a single folder and run just once, in timestamp order; as a result, when multiple branches each add a migration, the timestamps interleave. This breaks the "run once in order" assumption, and someone has to spend an afternoon renaming files to force a clean line. Prisma ORM avoids this with a different approach: the migration graph.
When this matters
You can build a whole app without thinking about the graph. However, the moment that any of the following occur and more than one line of history exists, it becomes vital:
- Two people (or two AI agents) change the schema on separate branches and merge.
- You need to roll a database back to an earlier schema and then forward again.
- A database is behind (a fresh clone, a long-lived staging box) and has to catch up through several changes.
- You want to point an environment at an exact schema state and prove in review that it is there.
If none of these apply yet, refer to How migrations work instead and revisit this when required.
The short version
Prisma ORM does not treat migrations as one timestamp-ordered list. Each migration records the schema state it starts from and the schema state it produces; those links form a graph. A database moves through the graph by following migrations from wherever it currently sits to wherever you tell it to go. Because every migration is anchored to real schema states rather than to its position in a folder, branches, merges, and rollbacks are all just paths through the same graph.
Usually you won't ever touch the graph directly. To see it, run migration graph. To check where a database is and what is pending, run migration status. To move a database to a specific state, run db migrate --to <ref>. The rest of this page explains what those commands are showing you.
Terms used on this page
| Term | Meaning |
|---|---|
| Contract | The schema you author, and the contract.json artifact it compiles to. |
| Contract state | One exact shape of the schema at a point in history. |
| Hash | A short fingerprint of a contract state, like 705b1a6…, the way a Git commit hash names an exact state of your files. |
| Node | A contract state in the graph, identified by its hash. |
| Edge | A migration. It moves the database from one node (from) to another (to). |
| Marker | A record Prisma ORM keeps inside the database naming the one node it currently matches. |
| Ref | A human-readable name for a node, like prod, stored as a file in your repo. |
How it works
Every time you emit your contract, you get a deterministic JSON artifact, and hashing it produces an identifier for that exact schema shape. Those hashes are the nodes of the graph.
A migration is an edge: it records the contract hash it starts from and the hash it moves the database to. Nothing about a migration's place in history comes from its file name: the timestamps in directory names are for humans, and the real linkage lives in the from/to hashes inside each migration.json.
A database always sits at exactly one node: its marker, a record Prisma ORM keeps in the database itself, holds the hash of the contract it currently matches. Applying a migration means walking one edge and moving the marker.
A worked example
Here is the Alice-and-Bob situation as an actual graph. Alice adds phone, Bob adds avatarUrl, and both branches merge:
bunx prisma@latest migration graph* f9a41d7 (prod)
|-\
|^| 20260303T1000_merge_alice 93be6c2 -> f9a41d7 1 ops
| |^ 20260303T1100_merge_bob 7e3fa7f -> f9a41d7 1 ops
* | 93be6c2
|^| 20260302T1000_alice_add_phone 705b1a6 -> 93be6c2 1 ops
| * 7e3fa7f
| |^ 20260302T1100_bob_add_avatar_url 705b1a6 -> 7e3fa7f 1 ops
|-/
* 705b1a6
|^ 20260301T1000_init - -> 705b1a6 1 ops
* -
1 space(s), 5 contract(s), 5 migration(s)Read it bottom-up. From an empty database (-), init establishes state 705b1a6. Alice and Bob both branch from 705b1a6: her migration produces 93be6c2, his produces 7e3fa7f. Each branch then has a merge migration into the combined state f9a41d7, which the prod ref points at. This shape is a diamond: one start, two parallel branches, one shared end.
The payoff is in the last column and the two merge edges. A database that follows Alice's branch is sitting at 93be6c2 and reaches f9a41d7 through merge_alice, while a database that follows Bob's is at 7e3fa7f and takes merge_bob. Neither developer needed to know about the other's migration, and no files were renamed to make the timestamps line up.
(The summary line counts contract spaces: independent migration lanes, one for your app plus one per database extension.)
What happens when you run db migrate
When you run db migrate, the runner walks the graph for you:
- It reads the database's marker to find which node the database is on now.
- It resolves your target: the merged state, a ref like
prod, or the latest state if you did not pass--to. - It finds a path of edges from the marker to the target.
- It applies each edge in order, moving the marker after each one.
- If any migration's precheck fails, it stops before running SQL and names the mismatch.
If two branch tips are both reachable and neither is clearly the target, the runner does not guess: it stops and asks you to pick one with --to.
Inspecting the graph
Four read-only commands, each answering a different question:
| Question you have | Command | Needs a database? |
|---|---|---|
| What does the whole topology look like? | migration graph | No |
| Which migration directories exist on disk? | migration list | No |
| Where is my database, and what is pending? | migration status | Yes |
| What has actually been applied, and when? | migration log | Yes |
migration graph also takes --json for machine-readable output, --dot to render with Graphviz, and --legend to print a key for the glyphs. migration log reads a ledger the runner appends to on every apply, so even a rollback shows up as history rather than as deleted history.
Try it on real fixtures
The Prisma ORM repo ships example graphs (a diamond, a wide fan-out, converging branches, rollback chains) as ready-to-render fixtures:
git clone https://github.com/prisma/orm
cd orm && pnpm install && pnpm -w build && pnpm install
cd examples/prisma-8-demo
npx prisma@latest migration graph --config fixtures/showcase/prisma.config.ts --legendSwap showcase for diamond, wide-fan, converging-branches, multi-branch, long-spine, or skip-rollback to explore each shape.
Name important states with refs
Raw hashes are awkward to type and impossible to remember, so name the states that matter. A ref is a human-readable pointer to a node, stored as a small file in migrations/app/refs/ and committed to your repo:
bunx prisma@latest migration ref set prod f9a41d7...
bunx prisma@latest migration ref list
bunx prisma@latest db migrate --to prodBecause refs are committed, "where production should be" is a reviewable fact in a pull request instead of tribal knowledge in someone's terminal history.
One ref name is special: a ref called db is the default starting point migration plan uses when you do not pass --from. Keep it current and planning stays incremental on its own. db init and db update advance it after a successful run, but only when you leave --db off and let the connection come from prisma.config.ts; with --db you have to add --advance-ref db. db sign advances it whenever it signs, --db or not. At apply time, db migrate --advance-ref db moves it; plain db migrate never does.
Three reserved tokens also work where a command accepts a contract reference, without setting up a ref:
@contract: the contract you most recently emitted.@db: whatever state the connected database's marker holds. Only the commands that connect accept it; the offline ones,migration ref setamong them, do not.@empty: the empty database, the origin with no prior storage state.
What the graph gives you
Parallel work without ordering conflicts
Alice and Bob each planned a migration from 705b1a6 on their own branches. In a timestamp-ordered system, whoever merges second inherits a broken history, but here both edges are in the graph and every environment finds its own path to the merged state. This matters even more when the "two developers" are two AI agents planning migrations concurrently, since neither has to know anything at all about the other.
History you can trust
Because each edge declares its from state, a migration will not run against a database in the wrong shape. If the marker does not match, the run stops before any SQL executes, with an error naming the mismatch.
Rollback as a normal move
An edge can point backwards, from a later contract state to an earlier one. Rolling back is not a special mode: it is planning one more migration, the destination of which is a previous state. See Rollbacks and recovery for more information.
More than one shape of history
The graph accommodates whatever your workflow produces: long linear spines, wide fan-outs where many branches leave one node, diamonds that converge again, and fast-forward edges that jump several states in one hop.
Baselines
A database that already exists can become the graph's starting node without replaying anything. When the graph is empty and the db ref names a contract whose snapshot is stored under migrations/snapshots/, one migration plan run writes two packages: a baseline from nothing to the ref's contract, and the delta from there to your emitted contract.
For a deployed database you must not touch, --from @empty is the explicit retrofit: bring the contract source back to the deployed state, emit, plan the baseline, then restore the current contract and plan the delta from it.
A baseline is never replayed against a database that already carries a marker: the runner starts at the marker and applies only the edges past it.
Database-specific details
The graph model is family-neutral: same nodes, same edges, same commands on every database. The parts that differ are in how the marker and ledger are stored.
- PostgreSQL: the marker lives in a
prisma_contract.markertable, and each apply runs inside a transaction guarded by an advisory lock so two runners cannot move the same database at once. - MongoDB: the marker and ledger live in a
_prisma_migrationscollection, updated with a compare-and-swap so a concurrent apply is rejected rather than interleaved.
Release-candidate limitations
The graph, pathfinding, refs, and the marker/ledger model all work now. However, some features that you might expect have not yet been built:
- No squash. You cannot yet collapse a long chain of migrations into one, including into a single baseline after the fact. The chain stays as-is.
- No split. You cannot yet break one large migration into smaller ones after the fact.
- Ambiguous targets need a choice. When two branch tips are both reachable,
db migrateasks you to pick one with--torather than guessing.
The graph has been designed with these in mind, but the commands have not shipped yet. Each migration page calls out what is missing rather than papering over it.
Common tasks
| Task | Command |
|---|---|
| See the whole graph | migration graph |
| Check where a database is | migration status |
| Move a database to a named state | db migrate --to prod |
Name a contract state prod | migration ref set prod <hash> |
| List the refs you have named | migration ref list |
| Roll a database back one state | db migrate --to <earlier-ref-or-hash> |
| Verify migration files and graph integrity | migration check |
Prompt your coding agent
Projects scaffolded with create-prisma@latest install Prisma ORM skills for your coding agent. Ask your agent to:
- "Draw the migration graph for this project and explain the branches."
- "Which contract state is the
prodref pointing at, and is the database there yet?" - "Two feature branches both added migrations. Check whether they conflict."
See also
- How migrations work: the plan-review-apply loop
- Applying a migration: how the runner walks the graph
- Studio with Prisma ORM: browse the same ledger visually in Prisma Studio, one contract edge per migration
- Rollbacks and recovery: backwards edges in practice
