Schema management in teams
When a team works on one schema, two people change it at the same time. In Prisma ORM, migrations form a graph rather than a numbered list, so parallel changes do not force anyone to rename files or rebuild history. This guide shows how that works day to day: what to commit, how to incorporate a teammate's migration, and how to resolve the case where two branches planned a migration from the same starting point and both merged.
Every command and every output below was run against a Git repository with three checkouts (two teammates and you), each with its own local PostgreSQL database.
- Node.js 24 or later
- A Prisma ORM project with a contract and at least one migration (the PostgreSQL quickstart gives you one)
- A PostgreSQL database per developer, reachable as
DATABASE_URL - Basic familiarity with Git branches and merges
- The migration loop:
contract emit,migration plan,db migrate
To delegate the pull-and-apply part of this guide to your coding agent, copy the prompt below and hand it over:
Bring my local database up to date with the migrations on this branch using Prisma ORM, following https://www.prisma.io/docs/guides/database/schema-changes.md.
1. Run `npx prisma migration status` and tell me what is pending. If it reports "Up to date", stop.
2. If it warns that there is no migration path from the database state to the contract, the graph has two branch tips. Run `npx prisma migration graph`, then plan one merge migration from each tip with `npx prisma migration plan --from <migration-dir> --name merge_<other-change>`, and show me both DDL previews before continuing.
3. Apply with `npx prisma db migrate --advance-ref db`, then run `npx prisma migration check` and `npx prisma db verify`.
4. If `migration plan` fails with MIGRATION.PLAN_ORIGIN_UNKNOWN, do not delete any migration directory. Find the contract state the database is on in the `npx prisma migration status` output, pass that hash with `--from`, and show me the error text. If you cannot tell which state the database is on, stop and ask me.Each migration directory records the contract state it starts from and the state it ends at, to, as hashes in its migration.json. Those links are the migration history. The timestamp in the directory name is for humans; nothing depends on it. That is why two developers can plan migrations from the same state on separate branches and merge without renaming anything. The migration graph explains the model in full.
Three commands answer the questions a team asks most:
| Question | Command | Needs a database? |
|---|---|---|
| What does the history look like? | npx prisma migration graph |
No |
| Where is my database, and what is pending? | npx prisma migration status |
Yes |
| Do the migration files on this branch hold together? | npx prisma migration check |
No |
Commit everything Prisma ORM needs to rebuild a database from scratch:
src/prisma/contract.prisma, the schema you authorsrc/prisma/contract.jsonandsrc/prisma/contract.d.ts, the emitted contract (generated, but committed;orm initmarks themlinguist-generatedin.gitattributes)migrations/app/, every migration directory:migration.ts,ops.json, andmigration.jsontogethermigrations/snapshots/, the contract snapshots migrations are typed againstmigrations/app/refs/, named refs, including thedbref described nextprisma.config.ts
Do not commit .env. orm init already lists it in .gitignore.
migration plan is offline. It never asks the database which contract state it matches, so you have to tell it which state to plan from. It resolves the origin in this order: an explicit --from, otherwise the ref named db in migrations/app/refs/db.json, otherwise the empty database. Keep the db ref pointing at the state your local database matches by applying with --advance-ref db:
bunx prisma db migrate --advance-ref dbpnpm prisma db migrate --advance-ref dbyarn prisma db migrate --advance-ref dbnpx prisma db migrate --advance-ref dbDo that every time you apply, and every plan chains from the right place without flags. Section 3 shows what happens when it cannot.
Do that every time you apply, and every plan chains from the right place without flags. Section 3 shows what happens when it cannot.
The project was created with npx prisma@latest orm init --yes --target postgres --authoring psl and has this contract:
// use prisma-8
model User {
id Int @id @default(autoincrement())
email String @unique
username String?
name String?
posts Post[]
createdAt TimestamptzString @default(now())
updatedAt temporal.updatedAtString()
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt TimestamptzString @default(now())
updatedAt temporal.updatedAtString()
}Its first migration was planned and applied on main with:
bunx prisma migration plan --name init
bunx prisma db migrate --advance-ref dbpnpm prisma migration plan --name init
pnpm prisma db migrate --advance-ref dbyarn prisma migration plan --name init
yarn prisma db migrate --advance-ref dbnpx prisma migration plan --name init
npx prisma db migrate --advance-ref db✔ Applied 1 migration(s) (6 operation(s)) across 1 contract space(s)
App space
├─ Create schema "public"
├─ Create table "Post"
├─ Create table "User"
├─ Add unique constraint on "User" (email)
├─ Create index "Post_authorId_idx_e47547ed" on "Post"
├─ Add foreign key "Post_authorId_fkey" on "Post"
└─ marker 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
✔ Advanced ref "db" → 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
→ Check every space against the database: {bin} migration statusThe {bin} in the hint is the CLI's placeholder for however you invoked it. Read it as npx prisma migration status. Every developer clones this repository, points DATABASE_URL in their .env at their own database, and runs npx prisma db migrate once to bring it to the same state.
✔ Applied 1 migration(s) (6 operation(s)) across 1 contract space(s)
App space
├─ Create schema "public"
├─ Create table "Post"
├─ Create table "User"
├─ Add unique constraint on "User" (email)
├─ Create index "Post_authorId_idx_e47547ed" on "Post"
├─ Add foreign key "Post_authorId_fkey" on "Post"
└─ marker 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
✔ Advanced ref "db" → 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
→ Check every space against the database: {bin} migration statusThe {bin} in the hint is the CLI's placeholder for however you invoked it. Read it as npx prisma migration status. Every developer clones this repository, points DATABASE_URL in their .env at their own database, and runs npx prisma db migrate once to bring it to the same state.
When a teammate merges a schema change, it arrives as three things: an edited contract.prisma, the re-emitted contract.json and contract.d.ts, and a new migration directory. Pull them, then ask Prisma which contract state your database matches:
git pullbunx prisma migration statuspnpm prisma migration statusyarn prisma migration statusnpx prisma migration statusIf nothing changed, you see the whole history marked applied:
○ 91e7f9f @contract @db (db)
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied
○ ∅
✔ Up to date@db marks the contract state your database matches, @contract the emitted contract, and (db) the state the db ref names. When migrations are pending, the tree flags each one with ⧗ pending and a summary line tells you how many. Apply them and update the db ref in one step:
bunx prisma db migrate --advance-ref dbpnpm prisma db migrate --advance-ref dbyarn prisma db migrate --advance-ref dbnpx prisma db migrate --advance-ref dbThere is no generate step after applying: your typed client reads contract.json, which you just pulled.
The rest of this guide follows three developers changing the same contract. Ania adds a favoriteColor field, Javier adds a Tag model, and you add a bestPacmanScore field:
model User {
id Int @id @default(autoincrement())
email String @unique
username String?
name String?
posts Post[]
createdAt TimestamptzString @default(now())
updatedAt temporal.updatedAtString()
}model User { id Int @id @default(autoincrement()) email String @unique username String? name String? favoriteColor String? // Added by Ania bestPacmanScore Int? // Added by you posts Post[] createdAt TimestamptzString @default(now()) updatedAt temporal.updatedAtString()}// Added by Javiermodel Tag { tagName String @id tagCategory String}Ania and Javier both branch from the same commit on main, where the db ref points at the init state.
On her branch, Ania adds the field:
model User {
/* ... */
favoriteColor String?
}Then she emits, plans, and applies to her own database:
bunx prisma contract emit
bunx prisma migration plan --name add_favorite_colorpnpm prisma contract emit
pnpm prisma migration plan --name add_favorite_coloryarn prisma contract emit
yarn prisma migration plan --name add_favorite_colornpx prisma contract emit
npx prisma migration plan --name add_favorite_color✔ Planned 1 operation(s)
migrations/app/20260910T1546_add_favorite_color
└─ Add column "favoriteColor" to "User"
from: 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
to: b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
app space: migrations/app/20260910T1546_add_favorite_color
ℹ DDL preview
ALTER TABLE "public"."User" ADD COLUMN "favoriteColor" text;The from: line is the init state, taken from the db ref. She applies and commits:
✔ Planned 1 operation(s)
migrations/app/20260910T1546_add_favorite_color
└─ Add column "favoriteColor" to "User"
from: 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
to: b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
app space: migrations/app/20260910T1546_add_favorite_color
ℹ DDL preview
ALTER TABLE "public"."User" ADD COLUMN "favoriteColor" text;The from: line is the init state, taken from the db ref. She applies and commits:
bunx prisma db migrate --advance-ref dbpnpm prisma db migrate --advance-ref dbyarn prisma db migrate --advance-ref dbnpx prisma db migrate --advance-ref db✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Add column "favoriteColor" to "User"
└─ marker b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
✔ Advanced ref "db" → b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1git add -A
git commit -m "Add User.favoriteColor"✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Add column "favoriteColor" to "User"
└─ marker b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
✔ Advanced ref "db" → b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1git add -A
git commit -m "Add User.favoriteColor"On his branch, from the same starting commit, Javier adds a model:
model Tag {
tagName String @id
tagCategory String
}bunx prisma contract emit
bunx prisma migration plan --name add_tag_modelpnpm prisma contract emit
pnpm prisma migration plan --name add_tag_modelyarn prisma contract emit
yarn prisma migration plan --name add_tag_modelnpx prisma contract emit
npx prisma migration plan --name add_tag_model✔ Planned 1 operation(s)
migrations/app/20260910T1547_add_tag_model
└─ Create table "Tag"
from: 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
to: 18b24e59a8b291a37b50e0d82c7afb58b314a2b64920d530627c73bd6e2d9768
app space: migrations/app/20260910T1547_add_tag_model
ℹ DDL preview
CREATE TABLE "public"."Tag" (
"tagCategory" text NOT NULL,
"tagName" text NOT NULL,
PRIMARY KEY ("tagName")
);His plan also starts from: the init state. He applies with db migrate --advance-ref db and commits. Both migrations now leave the same node and arrive at different ones. Neither developer knows about the other yet, and neither needs to.
✔ Planned 1 operation(s)
migrations/app/20260910T1547_add_tag_model
└─ Create table "Tag"
from: 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
to: 18b24e59a8b291a37b50e0d82c7afb58b314a2b64920d530627c73bd6e2d9768
app space: migrations/app/20260910T1547_add_tag_model
ℹ DDL preview
CREATE TABLE "public"."Tag" (
"tagCategory" text NOT NULL,
"tagName" text NOT NULL,
PRIMARY KEY ("tagName")
);His plan also starts from: the init state. He applies with db migrate --advance-ref db and commits. Both migrations now leave the same node and arrive at different ones. Neither developer knows about the other yet, and neither needs to.
Ania's branch merges into main first, as a fast-forward. Javier's merge conflicts:
git merge javier/tag-modelAuto-merging migrations/app/refs/db.json
CONFLICT (content): Merge conflict in migrations/app/refs/db.json
Auto-merging src/prisma/contract.d.ts
CONFLICT (content): Merge conflict in src/prisma/contract.d.ts
Auto-merging src/prisma/contract.json
CONFLICT (content): Merge conflict in src/prisma/contract.json
Auto-merging src/prisma/contract.prisma
Automatic merge failed; fix conflicts and then commit the result.Read the list carefully. contract.prisma, the file you author, merged cleanly: the two edits touch different parts of the schema. The three conflicts are all in files Prisma writes for you, and none of them needs hand editing:
contract.jsonandcontract.d.tsare emitted fromcontract.prisma. Re-emit and they are regenerated from the merged source, conflict markers and all:
bunx prisma contract emitpnpm prisma contract emityarn prisma contract emitnpx prisma contract emit✔ Emitted contract.json and contract.d.ts
storageHash: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bmigrations/app/refs/db.jsonrecords the contract state a local database last matched. Each branch pointed it at its own state, and neither is the state your database matches now. Take either side; you will set it correctly when you apply in section 3.5.
git checkout --ours migrations/app/refs/db.jsonDo not commit yet. The graph is not finished.
✔ Emitted contract.json and contract.d.ts
storageHash: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bmigrations/app/refs/db.jsonrecords the contract state a local database last matched. Each branch pointed it at its own state, and neither is the state your database matches now. Take either side; you will set it correctly when you apply in section 3.5.
git checkout --ours migrations/app/refs/db.jsonDo not commit yet. The graph is not finished.
Draw the graph:
bunx prisma migration graphpnpm prisma migration graphyarn prisma migration graphnpx prisma migration graph○ 1e5059c @contract
○ b29af39 (db)
│↑ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops
○ ∅
1 space(s), 4 contract(s), 3 migration(s)Read it bottom-up. From init (91e7f9f) two branches leave: Ania's ends at b29af39, Javier's at 18b24e5. The merged contract you just emitted, 1e5059c, sits at the top with no edge arriving at it. No database can reach it yet. migration status draws the same graph and marks where your database is, which is still on init (@db):
○ 1e5059c @contract
○ b29af39 (db)
│↑ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops
○ ∅
1 space(s), 4 contract(s), 3 migration(s)Read it bottom-up. From init (91e7f9f) two branches leave: Ania's ends at b29af39, Javier's at 18b24e5. The merged contract you just emitted, 1e5059c, sits at the top with no edge arriving at it. No database can reach it yet. migration status draws the same graph and marks where your database is, which is still on init (@db):
bunx prisma migration statuspnpm prisma migration statusyarn prisma migration statusnpx prisma migration status○ 1e5059c @contract
○ b29af39 (db)
│↑ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f @db
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied
○ ∅
⚠ No migration path from the database state (91e7f9f03580) to the application's contract (1e5059c1976c). Run `{bin} migration plan --name <name>` to author one.Do not follow that hint literally. Without --from, the planner takes the db ref as its origin, and after git checkout --ours that ref is Ania's tip, so migration plan --name merge_schema writes only the half of the merge her branch is missing. Javier's database would still have no path to the top. Had the ref still pointed at init, the planner would have started a third branch from the divergence point instead, with a warning that it forks the graph. Neither is the merge. Do not delete either branch's migration directory to get out of this: Ania's and Javier's databases have already applied those migrations. Name the origin yourself, once per tip.
○ 1e5059c @contract
○ b29af39 (db)
│↑ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f @db
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied
○ ∅
⚠ No migration path from the database state (91e7f9f03580) to the application's contract (1e5059c1976c). Run `{bin} migration plan --name <name>` to author one.Do not follow that hint literally. Without --from, the planner takes the db ref as its origin, and after git checkout --ours that ref is Ania's tip, so migration plan --name merge_schema writes only the half of the merge her branch is missing. Javier's database would still have no path to the top. Had the ref still pointed at init, the planner would have started a third branch from the divergence point instead, with a warning that it forks the graph. Neither is the merge. Do not delete either branch's migration directory to get out of this: Ania's and Javier's databases have already applied those migrations. Name the origin yourself, once per tip.
Plan one merge migration from each tip. Each one carries the change its branch is missing, and both arrive at the merged contract:
bunx prisma migration plan --from 20260910T1546_add_favorite_color --name merge_add_tag_modelpnpm prisma migration plan --from 20260910T1546_add_favorite_color --name merge_add_tag_modelyarn prisma migration plan --from 20260910T1546_add_favorite_color --name merge_add_tag_modelnpx prisma migration plan --from 20260910T1546_add_favorite_color --name merge_add_tag_model✔ Planned 1 operation(s)
migrations/app/20260910T1550_merge_add_tag_model
└─ Create table "Tag"
from: b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
to: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b✔ Planned 1 operation(s)
migrations/app/20260910T1550_merge_add_tag_model
└─ Create table "Tag"
from: b29af3990b528c7a67809a2ac609305c59aa9c0af1ed0e4591cc0a727b38b1d1
to: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bbunx prisma migration plan --from 20260910T1547_add_tag_model --name merge_add_favorite_colorpnpm prisma migration plan --from 20260910T1547_add_tag_model --name merge_add_favorite_coloryarn prisma migration plan --from 20260910T1547_add_tag_model --name merge_add_favorite_colornpx prisma migration plan --from 20260910T1547_add_tag_model --name merge_add_favorite_color✔ Planned 1 operation(s)
migrations/app/20260910T1550_merge_add_favorite_color
└─ Add column "favoriteColor" to "User"
from: 18b24e59a8b291a37b50e0d82c7afb58b314a2b64920d530627c73bd6e2d9768
to: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b--from accepts a migration directory name, as here, or a contract hash from the graph. The planner diffs the two contract snapshots and writes exactly the operations that are missing on that side. Review both DDL previews the way you would review any migration. The graph is now a diamond, and every database on either branch has a path to the top:
✔ Planned 1 operation(s)
migrations/app/20260910T1550_merge_add_favorite_color
└─ Add column "favoriteColor" to "User"
from: 18b24e59a8b291a37b50e0d82c7afb58b314a2b64920d530627c73bd6e2d9768
to: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b--from accepts a migration directory name, as here, or a contract hash from the graph. The planner diffs the two contract snapshots and writes exactly the operations that are missing on that side. Review both DDL previews the way you would review any migration. The graph is now a diamond, and every database on either branch has a path to the top:
bunx prisma migration graphpnpm prisma migration graphyarn prisma migration graphnpx prisma migration graph○ 1e5059c @contract
│─╮
│↑│ 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 ops
│ │↑ 20260910T1550_merge_add_favorite_color 18b24e5 → 1e5059c 1 ops
○ │ b29af39 (db)
│↑│ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops
○ ∅
1 space(s), 5 contract(s), 5 migration(s)Your own database is still on init, so two migrations are pending for it. Apply them and let --advance-ref db set the db ref to where the database actually ends up, which also settles the db.json conflict from section 3.3:
○ 1e5059c @contract
│─╮
│↑│ 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 ops
│ │↑ 20260910T1550_merge_add_favorite_color 18b24e5 → 1e5059c 1 ops
○ │ b29af39 (db)
│↑│ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops
○ ∅
1 space(s), 5 contract(s), 5 migration(s)Your own database is still on init, so two migrations are pending for it. Apply them and let --advance-ref db set the db ref to where the database actually ends up, which also settles the db.json conflict from section 3.3:
bunx prisma db migrate --advance-ref dbpnpm prisma db migrate --advance-ref dbyarn prisma db migrate --advance-ref dbnpx prisma db migrate --advance-ref db✔ Applied 2 migration(s) (2 operation(s)) across 1 contract space(s)
App space
├─ Add column "favoriteColor" to "User"
├─ Create table "Tag"
└─ marker 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b
✔ Advanced ref "db" → 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bThe runner walked init → add_favorite_color → merge_add_tag_model. It could equally have walked the other side; both paths end at the same contract. Now commit the merge, the re-emitted contract, both merge migrations, and the db ref together:
git add -A
git commit -m "Merge javier/tag-model and plan merge migrations"✔ Applied 2 migration(s) (2 operation(s)) across 1 contract space(s)
App space
├─ Add column "favoriteColor" to "User"
├─ Create table "Tag"
└─ marker 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b
✔ Advanced ref "db" → 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bThe runner walked init → add_favorite_color → merge_add_tag_model. It could equally have walked the other side; both paths end at the same contract. Now commit the merge, the re-emitted contract, both merge migrations, and the db ref together:
git add -A
git commit -m "Merge javier/tag-model and plan merge migrations"3.6. Every database runs only the migrations it needs
Section titled “3.6. Every database runs only the migrations it needs”Ania pulls main. Her database matches b29af39, so only the migration that adds Javier's table is pending for her:
bunx prisma migration statuspnpm prisma migration statusyarn prisma migration statusnpx prisma migration status○ 1e5059c @contract (db)
│─╮
│↑│ 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 ops ⧗ pending
│ │↑ 20260910T1550_merge_add_favorite_color 18b24e5 → 1e5059c 1 ops
○ │ b29af39 @db
│↑│ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops ✓ applied
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied
○ ∅
⚠ 1 pending: run `{bin} db migrate --to 1e5059c1976c`○ 1e5059c @contract (db)
│─╮
│↑│ 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 ops ⧗ pending
│ │↑ 20260910T1550_merge_add_favorite_color 18b24e5 → 1e5059c 1 ops
○ │ b29af39 @db
│↑│ 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops ✓ applied
│ ○ 18b24e5
│ │↑ 20260910T1547_add_tag_model 91e7f9f → 18b24e5 1 ops
│─╯
○ 91e7f9f
│↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied
○ ∅
⚠ 1 pending: run `{bin} db migrate --to 1e5059c1976c`bunx prisma db migrate --advance-ref dbpnpm prisma db migrate --advance-ref dbyarn prisma db migrate --advance-ref dbnpx prisma db migrate --advance-ref db✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Create table "Tag"
└─ marker 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bJavier does the same, and for him the pending migration is merge_add_favorite_color, which adds Ania's column. Nobody re-created anything, and the database's own ledger shows the route each one took:
✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Create table "Tag"
└─ marker 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60bJavier does the same, and for him the pending migration is merge_add_favorite_color, which adds Ania's column. Nobody re-created anything, and the database's own ledger shows the route each one took:
bunx prisma migration logpnpm prisma migration logyarn prisma migration lognpx prisma migration logApplied at Migration Change Ops
2026-09-10 17:45:41 +02:00 20260910T1543_init ∅ → 91e7f9f 6 ops
2026-09-10 17:46:17 +02:00 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
2026-09-10 17:52:47 +02:00 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 opsApplied at Migration Change Ops
2026-09-10 17:45:41 +02:00 20260910T1543_init ∅ → 91e7f9f 6 ops
2026-09-10 17:46:17 +02:00 20260910T1546_add_favorite_color 91e7f9f → b29af39 1 ops
2026-09-10 17:52:47 +02:00 20260910T1550_merge_add_tag_model b29af39 → 1e5059c 1 opsWith main merged and your database on the merged state, add your own field. Always pull and apply before you plan, so your plan chains from the state everyone shares:
git pullbunx prisma migration statuspnpm prisma migration statusyarn prisma migration statusnpx prisma migration statusmodel User {
/* ... */
favoriteColor String?
bestPacmanScore Int?
}bunx prisma contract emit
bunx prisma migration plan --name add_best_pacman_scorepnpm prisma contract emit
pnpm prisma migration plan --name add_best_pacman_scoreyarn prisma contract emit
yarn prisma migration plan --name add_best_pacman_scorenpx prisma contract emit
npx prisma migration plan --name add_best_pacman_score✔ Planned 1 operation(s)
migrations/app/20260910T1554_add_best_pacman_score
└─ Add column "bestPacmanScore" to "User"
from: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b
to: 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440
app space: migrations/app/20260910T1554_add_best_pacman_score
ℹ DDL preview
ALTER TABLE "public"."User" ADD COLUMN "bestPacmanScore" int4;No --from was needed: the db ref points at the merged state because the last apply advanced it. Check the from: line anyway. If it does not name the state you expect, stop and read Common gotchas.
✔ Planned 1 operation(s)
migrations/app/20260910T1554_add_best_pacman_score
└─ Add column "bestPacmanScore" to "User"
from: 1e5059c1976c2439863eae8ac13211916ca411614c917394c413346f0d00f60b
to: 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440
app space: migrations/app/20260910T1554_add_best_pacman_score
ℹ DDL preview
ALTER TABLE "public"."User" ADD COLUMN "bestPacmanScore" int4;No --from was needed: the db ref points at the merged state because the last apply advanced it. Check the from: line anyway. If it does not name the state you expect, stop and read Common gotchas.
bunx prisma db migrate --advance-ref db
bunx prisma migration checkpnpm prisma db migrate --advance-ref db
pnpm prisma migration checkyarn prisma db migrate --advance-ref db
yarn prisma migration checknpx prisma db migrate --advance-ref db
npx prisma migration check✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Add column "bestPacmanScore" to "User"
└─ marker 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440
✔ Advanced ref "db" → 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440✔ All checks passedmigration check is offline and verifies every migration's hash and the graph's integrity. Run it after you edit a migration by hand, so an ops.json or migration.json that no longer matches its hash shows up before you commit. A pipeline does not need it, because db migrate refuses such a migration too.
migration check reads only the files, so it cannot tell you whether a database matches them. db verify does. Run npx prisma db verify --db "$DATABASE_URL" after db migrate, and it fails if the database's marker or its tables differ from the contract you emitted. A pipeline that migrates a shared database runs db migrate alone; it refuses a database whose marker is outside your history with MIGRATION.MARKER_MISMATCH before it runs anything. Migrate a shared database from a workflow shows the job.
✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Add column "bestPacmanScore" to "User"
└─ marker 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440
✔ Advanced ref "db" → 4961e5b3ceb00cb24a60245343f288095c5a00fc2344d02db1b68509a8b91440✔ All checks passedmigration check is offline and verifies every migration's hash and the graph's integrity. Run it after you edit a migration by hand, so an ops.json or migration.json that no longer matches its hash shows up before you commit. A pipeline does not need it, because db migrate refuses such a migration too.
migration check reads only the files, so it cannot tell you whether a database matches them. db verify does. Run npx prisma db verify --db "$DATABASE_URL" after db migrate, and it fails if the database's marker or its tables differ from the contract you emitted. A pipeline that migrates a shared database runs db migrate alone; it refuses a database whose marker is outside your history with MIGRATION.MARKER_MISMATCH before it runs anything. Migrate a shared database from a workflow shows the job.
Commit the same set of files your teammates did:
git add src/prisma/contract.prisma src/prisma/contract.json src/prisma/contract.d.ts migrations
git commit -m "Add User.bestPacmanScore"The final graph on main is a diamond with one more step on top, and your application code can use all three changes right away:
const user = await db.orm.public.User.create({
email: "pacman@prisma.io",
favoriteColor: "yellow",
bestPacmanScore: 3333360,
});
const tag = await db.orm.public.Tag.create({ tagName: "arcade", tagCategory: "games" });Run npx prisma@latest init once to install the Prisma ORM skills for your coding agent and keep them matching your installed packages. Prompts that map to this guide:
- "Using the prisma-8 skill, check whether my database is behind the migrations on this branch and apply what is pending."
- "Two branches both added migrations from the same state. Draw the migration graph and plan the merge migrations from each tip."
- "Run
migration checkand explain any integrity failure before I open this pull request."
- The migration graph: refs, markers, and how
db migratefinds a path - Applying a migration: the status, preview, apply rhythm for staging and production
- Rollbacks and recovery: planning a backwards edge when a merged change has to come out
- Editing a migration: backfills and raw SQL when a schema change needs a data step