The migration graph
Say you are Alice, and on your branch you add a phone field to your contract, the contract.prisma file that replaced schema.prisma. Bob adds an avatar field on his branch, and both branches merge the same afternoon. Your laptop, Bob's laptop, staging, and production each hold a different version of the database, and each one needs the merged version without losing work or repeating a change.
Prisma ORM 7 keeps migrations as timestamped SQL directories and applies them in name order, so where a migration sits in the history is decided by its name. In Prisma ORM 8, each migration records the contract versions it starts and ends at instead, so migrations are linked to each other rather than ordered by time, and those links are the migration graph.
Most of the time you can build a whole app without thinking about the graph at all, because a single line of migrations needs no explaining. It is worth understanding when you are in one of these situations:
- Two people, or two AI agents, change the contract on separate branches and merge.
- You roll a database back to an earlier version of your contract and then forward again.
- A database is several contract states out of date, after a fresh clone or on a long-lived staging database.
Each migration records the contract state it starts from and the state it ends at, so a migration is a link between two named versions of your contract rather than a step in a numbered queue. That is why branching, merging, and rolling back are all the same act: you plan one more migration with npx prisma migration plan and tell it which state to start from.
| Term | Meaning |
|---|---|
| Contract | Your contract.prisma file, compiled to contract.json. |
| Contract state | One version of your contract, named by its hash. |
| Hash | An identifier computed from the database layout your contract describes, not from the text of contract.prisma. The graph shows its first seven characters, like 4437973. |
| Node | A contract state, drawn as a ○ row in migration graph output. |
| Edge | A migration, drawn as a ↑, ↓, or ⟲ row. Applying it changes the database from one node's state to the other's. |
| Marker | The record in the database of which contract state it matches. Reading it does not check the tables. |
| Ledger | The database's own list of every migration applied to it, and when each ran. |
| Ref | A name for a contract state, like prod, stored as a file in migrations/app/refs/. |
| Contract space | A separate migration history with its own directory in migrations/. Your app's is migrations/app/. Each Prisma ORM extension package that ships migrations, such as pgvector support, has its own, and one db migrate run applies all of them, as Extension spaces shows. |
A migration on disk is a directory in migrations/app/, and what makes it part of a graph rather than an item in a list is that it names both ends of its own change. Its ops.json lists the operations, the steps it runs, such as adding a column, and What a migration contains lists the rest of its files. Its migration.json records the hash it starts from and the hash it ends at, to, so a migration that ends where another one starts is joined to it. To change what a migration does, edit its migration.ts and recompile with node migrations/app/<dir>/migration.ts, as Editing a migration shows.
States are nodes, migrations are edgesStep 1 of 3
Every emitted contract hashes to an identifier for that exact schema state. A migration is an edge from one state to the next.
A database with no marker counts as empty, so db migrate starts it from the first migration. If the database already has tables but no marker, Prisma ORM still counts it as empty, which is not what you want, so read Baselines before you run anything against it.
Here is the situation from the top of the page, after Bob's branch merged first. Name important states with refs explains @contract and (prod):
bunx prisma migration graphpnpm prisma migration graphyarn prisma migration graphnpx prisma migration graph│ migrations: migrations
○ e377d00 @contract
│↑ 20260922T0627_alice_merge 1a76a3c → e377d00 1 ops
○ │ 5e1f082
│↑│ 20260922T0627_alice_add_phone 4437973 → 5e1f082 1 ops
│ ○ 1a76a3c (prod)
│ │↑ 20260922T0627_bob_add_avatar 4437973 → 1a76a3c 1 ops
│─╯
○ 4437973
│↑ 20260922T0626_init ∅ → 4437973 3 ops
○ ∅
1 space(s), 5 contract(s), 4 migration(s)Read it from the bottom up, because the earliest state is the bottom row and every row above it is a later one. Each ↑ row shows a migration's directory name, its start and end hashes, and its operation count, and --legend prints a key to the other symbols.
From an empty database (∅), init produces contract state 4437973. Alice's migration starts there and produces 5e1f082, and Bob's starts from the same state and produces 1a76a3c, which is why the drawing splits in two. Bob merged first, so main now points the prod ref at 1a76a3c, and production will be migrated there. Alice's branch still holds a migration that starts from 4437973, which is no longer the head.
After Alice rebases onto main, her contract.prisma holds both fields, phone and avatar, and production is at 1a76a3c, where prod points. So the migration production needs is one from prod to that merged contract. If git reports a conflict in contract.prisma, contract.json, or contract.d.ts, she resolves it in contract.prisma and runs npx prisma contract emit, which rewrites the other two. She accepts main's refs/prod.json as it is, and if refs/db.json conflicts too, she can take either side, because the db update in the last step below points it at the merged state. Then she plans one migration from prod:
│ migrations: migrations
○ e377d00 @contract
│↑ 20260922T0627_alice_merge 1a76a3c → e377d00 1 ops
○ │ 5e1f082
│↑│ 20260922T0627_alice_add_phone 4437973 → 5e1f082 1 ops
│ ○ 1a76a3c (prod)
│ │↑ 20260922T0627_bob_add_avatar 4437973 → 1a76a3c 1 ops
│─╯
○ 4437973
│↑ 20260922T0626_init ∅ → 4437973 3 ops
○ ∅
1 space(s), 5 contract(s), 4 migration(s)Read it from the bottom up, because the earliest state is the bottom row and every row above it is a later one. Each ↑ row shows a migration's directory name, its start and end hashes, and its operation count, and --legend prints a key to the other symbols.
From an empty database (∅), init produces contract state 4437973. Alice's migration starts there and produces 5e1f082, and Bob's starts from the same state and produces 1a76a3c, which is why the drawing splits in two. Bob merged first, so main now points the prod ref at 1a76a3c, and production will be migrated there. Alice's branch still holds a migration that starts from 4437973, which is no longer the head.
After Alice rebases onto main, her contract.prisma holds both fields, phone and avatar, and production is at 1a76a3c, where prod points. So the migration production needs is one from prod to that merged contract. If git reports a conflict in contract.prisma, contract.json, or contract.d.ts, she resolves it in contract.prisma and runs npx prisma contract emit, which rewrites the other two. She accepts main's refs/prod.json as it is, and if refs/db.json conflicts too, she can take either side, because the db update in the last step below points it at the merged state. Then she plans one migration from prod:
bunx prisma migration plan --name alice_merge --from prodpnpm prisma migration plan --name alice_merge --from prodyarn prisma migration plan --name alice_merge --from prodnpx prisma migration plan --name alice_merge --from prodThat is alice_merge in the drawing, from 1a76a3c to the merged contract e377d00, and it adds phone to a database that already has avatar. migration plan always ends at whatever is in contract.json, which now holds the merged contract. If her old migration.ts had operations she wrote by hand, such as a dataTransform, she copies them into the new file and recompiles it, because the planner derives the schema changes from the contract but cannot carry over what she wrote.
Her old migration, alice_add_phone, can stay on disk or be deleted. Either way it never runs, because db migrate follows the path from the database's marker to its target, and 5e1f082 is not on the path from prod to @contract, so it cannot run out of order. Her own development database is the one database that is at 5e1f082, and the quickest way to bring it to the merged contract is npx prisma db update, which changes a development database directly and points the db ref at the new state, so her next plan starts from the right place.
That is alice_merge in the drawing, from 1a76a3c to the merged contract e377d00, and it adds phone to a database that already has avatar. migration plan always ends at whatever is in contract.json, which now holds the merged contract. If her old migration.ts had operations she wrote by hand, such as a dataTransform, she copies them into the new file and recompiles it, because the planner derives the schema changes from the contract but cannot carry over what she wrote.
Her old migration, alice_add_phone, can stay on disk or be deleted. Either way it never runs, because db migrate follows the path from the database's marker to its target, and 5e1f082 is not on the path from prod to @contract, so it cannot run out of order. Her own development database is the one database that is at 5e1f082, and the quickest way to bring it to the merged contract is npx prisma db update, which changes a development database directly and points the db ref at the new state, so her next plan starts from the right place.
npx prisma db migrate works out for itself which migrations your database still needs. It starts from the state in the marker and runs the migrations on the path to its target, which is contract.json unless --to names a contract state instead. If an operation fails, the run stops there, and When something goes wrong explains how to re-run while Recovery explains what to do next.
Once your history has branches, more than one path can lead to the target, so db migrate has to pick one. It takes the path with the fewest migrations, and on a tie the migration with the earlier createdAt in its migration.json. That is also why a migration whose end state leads nowhere is never run: a database at 4437973 runs bob_add_avatar and then alice_merge, and never alice_add_phone, because no path through it reaches the target. The choice does not change where you end up, because both paths end at the same contract state, and db migrate checks the tables against that state before it updates the marker. To see the path it picked before anything runs, use npx prisma db migrate --show.
When no chain of migrations leads from the marker to the target, the run fails with an error whose code is MIGRATION.PATH_UNREACHABLE, and that usually means one of two things. Either --to names a state you did not intend, which is worth checking first, or the migration you need has not been planned yet. In the second case, plan it from the hash the marker holds with npx prisma migration plan --from <hash> --name <name>, and to find that hash run npx prisma migration status, which prints it next to its @db label.
| Question you have | Command | Needs a database? |
|---|---|---|
| What does the whole graph look like? | npx prisma migration graph |
No |
| Which migration directories exist on disk? | npx prisma migration list |
No |
Was an ops.json or migration.json edited by hand, or does a ref name a missing state? |
npx prisma migration check |
No |
Which contract state does my database match, and what would db migrate run next? |
npx prisma migration status |
Yes |
| What has actually been applied, and when? | npx prisma migration log |
Yes |
migration log reads the ledger, so it tells you what actually ran rather than what should have run, and a rollback appears there as one more applied migration instead of erasing anything. Run migration check before you commit a migration you edited, and after you move a ref. It catches an ops.json or migration.json that was changed by hand, and a ref naming a state that does not exist, and it needs no database connection. A deploy pipeline does not need it: db migrate --to <ref> refuses a hand-edited migration file on its own. See Reviewing what you planned for its exit codes.
The Prisma ORM repository has branched histories, including a diamond fixture with the same init, alice_add_phone, and bob_add_avatar migrations, in examples/prisma-8-demo/fixtures/. To draw one, build the repository and pass the example's prisma.config.ts to migration graph with --config.
Hashes are hard to remember and hard to talk about, so Prisma ORM lets you give the states that matter a name of your own. A ref is that name: you create one and point it at a state with npx prisma migration ref set, and then you pass the name instead of a hash, for example to db migrate --to. The name prod below is only an example:
bunx prisma migration ref set prod 1a76a3c
bunx prisma migration ref list
bunx prisma db migrate --to prodpnpm prisma migration ref set prod 1a76a3c
pnpm prisma migration ref list
pnpm prisma db migrate --to prodyarn prisma migration ref set prod 1a76a3c
yarn prisma migration ref list
yarn prisma db migrate --to prodnpx prisma migration ref set prod 1a76a3c
npx prisma migration ref list
npx prisma db migrate --to prodA ref named for an environment, such as prod or staging, names the contract state your deploy pipeline will migrate that environment to. It is a promise the repository makes, not a record of what is deployed; the marker in the database records that. Point it at the new state when a change merges to main, and have the pipeline run db migrate --to prod so it never applies more than the repository has promised.
To say which state a ref should point at, ref set takes a hash or a prefix of one, a ref name, a migration directory name, or <dir>^, but none of the @ tokens below. Once the ref exists, db migrate --to prod applies migrations until the database matches the state prod names, and the database it changes is the one prisma.config.ts connects to, unless you pass a connection string with --db.
Some states you only need to refer to once, so Prisma ORM reserves a few tokens that start with @. Each names a contract state without creating a ref:
@contract: the contract incontract.json.@db: the contract state in the marker of the database you are connected to. Thedbref is a file, so it can name a different state.@empty: the empty database, before any migration.
Not every command takes every token. migration plan is offline and cannot read a database, so of the three its --from takes only @empty; each command's reference lists what it accepts, such as db migrate --show. So if you want to see the path from the contract you have now to the state prod names, without changing anything, run npx prisma db migrate --show --from @contract --to prod.
A ref named for an environment, such as prod or staging, names the contract state your deploy pipeline will migrate that environment to. It is a promise the repository makes, not a record of what is deployed; the marker in the database records that. Point it at the new state when a change merges to main, and have the pipeline run db migrate --to prod so it never applies more than the repository has promised.
To say which state a ref should point at, ref set takes a hash or a prefix of one, a ref name, a migration directory name, or <dir>^, but none of the @ tokens below. Once the ref exists, db migrate --to prod applies migrations until the database matches the state prod names, and the database it changes is the one prisma.config.ts connects to, unless you pass a connection string with --db.
Some states you only need to refer to once, so Prisma ORM reserves a few tokens that start with @. Each names a contract state without creating a ref:
@contract: the contract incontract.json.@db: the contract state in the marker of the database you are connected to. Thedbref is a file, so it can name a different state.@empty: the empty database, before any migration.
Not every command takes every token. migration plan is offline and cannot read a database, so of the three its --from takes only @empty; each command's reference lists what it accepts, such as db migrate --show. So if you want to see the path from the contract you have now to the state prod names, without changing anything, run npx prisma db migrate --show --from @contract --to prod.
Two branches can plan migrations at the same time without either one knowing about the other, because nothing about a migration depends on when it was planned. Cleaning up afterwards is one migration plan --from prod, as A worked example shows.
A migration only ever runs against a database that matches the state it starts from. Before any operation runs, db migrate checks that the marker is a state in your migration history and stops if it is not. That check reads only the marker, not the tables. db migrate checks the tables only after it has run operations, and never when it has nothing to run, so to compare the tables against your contract directly, run npx prisma db verify --schema-only.
That marker check is also what you run into after using npx prisma db update, which, like Prisma ORM 7's db push, changes a database to match the contract without writing a migration. Because the contract it applied is one that no migration ends at, your next db migrate run stops at the check. Drift explains what to do next.
Undoing a change is not a special mode you switch into, because a migration can go backwards, from a later contract state to an earlier one, and you plan and apply it like any other migration. Rollbacks and recovery shows the commands, including reverting your contract before you apply the rollback.
Straight lines, branches, and branches that join again all use the same commands, so there is nothing new to learn the first time your history stops being a straight line.
The word "baseline" turns up in several different places, and which one you are looking at depends on where you saw it:
- A kind of migration. A first migration from an empty database to a contract state, such as
initabove. - A label in command output.
(baseline)inmigration planoutput means the migration starts from an empty database. - A Prisma ORM 7 task. What Prisma ORM 7 called baselining, marking an existing database as already migrated, is
npx prisma db signin Prisma ORM 8.
Reach for npx prisma db sign when a database has no marker and its tables already match your contract. It checks the tables, writes the marker, and points the db ref at the signed contract. Signing alone is not enough to start migrating, though, because db migrate refuses a signed database until a migration in your history ends at the signed state. Your next migration plan writes that one for you, because the db ref now names the signed state; The automatic baseline explains what it writes. For a database Prisma ORM 7 migrated, follow Transfer migration ownership instead.
Nothing above changes with your database, because the graph and the commands are the same on every database that Prisma ORM 8 supports. What does differ is where the marker and ledger are stored, and how much of a failed db migrate run is left behind, which When something goes wrong covers.
- PostgreSQL: the marker is the
prisma_contract.markertable and the ledger is theprisma_contract.ledgertable. - MongoDB: the marker and ledger are documents in a
_prisma_migrationscollection, which is new in Prisma ORM 8 because Prisma ORM 7 had no migrations on MongoDB.
npx prisma migration new writes an empty migration for a change you write yourself, such as a data update. The migration always ends at the contract state in your current contract.json. Without --from, it starts where migration plan would: at the db ref, or at an empty database when there are no migrations and no db ref yet. When there are migrations but no db ref, it stops and asks for --from. migration new lists every case.
Unlike the --from of migration plan, the --from of migration new takes only a contract hash that an existing migration ends at, which is the to hash in that migration's migration.json. You can shorten the hash to its first characters, such as the 7 that migration graph shows, as long as they match only one migration's to hash. It does not take a ref name, a migration directory name, or an @ name such as @db. So to start from e377d00 in the drawing above, run npx prisma migration new --name backfill --from e377d00. Because e377d00 is also the @contract state, that migration starts and ends at the same state, which is what a data-only migration does.
The graph itself, the way db migrate chooses which migrations to run, refs, the marker, and the ledger all work today. These are not built yet:
- No squash. You cannot yet collapse a long chain of migrations into one.
- No split. You cannot yet break one large migration into smaller ones after the fact.
| Task | Command |
|---|---|
| Create a migration after you change the contract | npx prisma migration plan --name <name> |
| Plan the migration production needs after a merge | npx prisma migration plan --name <name> --from prod |
| Apply migrations to your development database | npx prisma db migrate --advance-ref db |
| See the whole graph | npx prisma migration graph |
| See which contract state a database matches | npx prisma migration status |
| Apply migrations until a database matches a named state | npx prisma db migrate --to prod |
Name a contract state prod |
npx prisma migration ref set prod <hash> |
| List the refs you have named | npx prisma migration ref list |
Plan a rollback of the migration in <dir> |
npx prisma migration plan --from <dir> --to <dir>^ --name <name> |
| Apply a rollback migration you planned | npx prisma db migrate --to <earlier-ref-or-hash> |
| Check migration files and refs offline | npx prisma migration check |
Projects created with npm create prisma@latest include the Prisma ORM skills for your coding agent. In an existing project, run npx prisma skills sync. Ask your agent to:
- "Draw the migration graph for this project and explain the branches."
- "Which contract state is the
prodref pointing at, and does the database match it?" - "Two feature branches both added migrations. Draw the graph and plan the one migration production needs from the
prodref."
- How migrations work: what a migration contains, and how you plan, review, and apply one
- Applying a migration: running
db migratein development and production - Studio with Prisma ORM: browse the same ledger visually in Prisma Studio, one entry per migration
- Rollbacks and recovery: planning and applying a migration back to an earlier state