Applying a migration
Once you have planned migrations with npx prisma migration plan, npx prisma db migrate is the command that runs them against a database, until that database matches your contract. It replaces Prisma ORM 7's prisma migrate deploy, and works on the supported databases, PostgreSQL and MongoDB, with SQLite still experimental.
bunx prisma db migratepnpm prisma db migrateyarn prisma db migratenpx prisma db migratePrisma ORM decides what to run by comparing two records: the contract you want, and the contract state the database has. Each version of your contract, the contract.prisma file, is a contract state, and the database itself stores a marker, which is the record of which contract state that database matches. So when you run db migrate, it reads the marker to find the contract state the database already matches, and then runs the migrations from that state to the contract you last emitted with npx prisma contract emit.
migration status, migration log, and the db commands connect to a database using db.connection in prisma.config.ts, as The data contract shows. When you want to work against a different database, production rather than your own machine for example, pass --db with that database's connection string, as in --db "$PRODUCTION_DATABASE_URL".
When a run succeeds, it shows you what it did: each migration's operations, and then the marker it wrote:
✔ Applied 1 migration(s) (3 operation(s)) across 1 contract space(s)
App space
├─ Add column "displayName" to "user"
├─ Data transform: backfill-user-displayName
├─ ⚠ Set NOT NULL on "user"."displayName"
└─ marker 866ab885a8a4bd8d9f5adc47784c068be7895a7182c55cc29f9f9967d3019484
⚠ This migration contains destructive operations that may cause data loss.Each ├─ line under App space is an operation, one step of an app migration. A data transform changes existing rows, here filling in displayName, and you write its queries in migration.ts, as Editing a migration shows. A ⚠ marks a destructive operation, one that removes or changes something that already exists, such as setting NOT NULL, which deletes no rows but still counts. db migrate applies destructive operations without stopping to ask you, so the time to catch one is while you are reviewing the migration: run npx prisma migration show <dir> with the migration's directory name in migrations/app/.
Prisma ORM decides what to run by comparing two records: the contract you want, and the contract state the database has. Each version of your contract, the contract.prisma file, is a contract state, and the database itself stores a marker, which is the record of which contract state that database matches. So when you run db migrate, it reads the marker to find the contract state the database already matches, and then runs the migrations from that state to the contract you last emitted with npx prisma contract emit.
migration status, migration log, and the db commands connect to a database using db.connection in prisma.config.ts, as The data contract shows. When you want to work against a different database, production rather than your own machine for example, pass --db with that database's connection string, as in --db "$PRODUCTION_DATABASE_URL".
When a run succeeds, it shows you what it did: each migration's operations, and then the marker it wrote:
✔ Applied 1 migration(s) (3 operation(s)) across 1 contract space(s)
App space
├─ Add column "displayName" to "user"
├─ Data transform: backfill-user-displayName
├─ ⚠ Set NOT NULL on "user"."displayName"
└─ marker 866ab885a8a4bd8d9f5adc47784c068be7895a7182c55cc29f9f9967d3019484
⚠ This migration contains destructive operations that may cause data loss.Each ├─ line under App space is an operation, one step of an app migration. A data transform changes existing rows, here filling in displayName, and you write its queries in migration.ts, as Editing a migration shows. A ⚠ marks a destructive operation, one that removes or changes something that already exists, such as setting NOT NULL, which deletes no rows but still counts. db migrate applies destructive operations without stopping to ask you, so the time to catch one is while you are reviewing the migration: run npx prisma migration show <dir> with the migration's directory name in migrations/app/.
Before you apply migrations to staging or production, run the following commands in order, so that you check the migration files, then see what would run, and only then run it:
# 1. Were any migration files edited by hand or deleted? Needs no database.
bunx prisma migration check
# 2. Which migrations would run?
bunx prisma db migrate --show --db "$PRODUCTION_DATABASE_URL"
# 3. Run them.
bunx prisma db migrate --db "$PRODUCTION_DATABASE_URL"# 1. Were any migration files edited by hand or deleted? Needs no database.
pnpm prisma migration check
# 2. Which migrations would run?
pnpm prisma db migrate --show --db "$PRODUCTION_DATABASE_URL"
# 3. Run them.
pnpm prisma db migrate --db "$PRODUCTION_DATABASE_URL"# 1. Were any migration files edited by hand or deleted? Needs no database.
yarn prisma migration check
# 2. Which migrations would run?
yarn prisma db migrate --show --db "$PRODUCTION_DATABASE_URL"
# 3. Run them.
yarn prisma db migrate --db "$PRODUCTION_DATABASE_URL"# 1. Were any migration files edited by hand or deleted? Needs no database.
npx prisma migration check
# 2. Which migrations would run?
npx prisma db migrate --show --db "$PRODUCTION_DATABASE_URL"
# 3. Run them.
npx prisma db migrate --db "$PRODUCTION_DATABASE_URL"The examples in the rest of this section use a project with two migrations, init and add_user_phone, where the production database has applied only init.
npx prisma migration status --db "$PRODUCTION_DATABASE_URL" never changes the database, so you can run it against production whenever you want to know which contract state that database matches. It draws your migration history, marks each migration as applied or pending, and shows which contract state the database matches:
○ 967cb9f @contract
│↑ 20260707T1006_add_user_phone 2d2cec8 → 967cb9f 1 ops ⧗ pending
○ 2d2cec8 @db (db)
│↑ 20260707T1005_init ∅ → 2d2cec8 2 ops ✓ applied
○ ∅Read the diagram from the bottom up, because it starts with an empty database and ends with the contract you last emitted: ∅ is an empty database, each ○ line is a contract state shown by the first 7 characters of its hash, and each │↑ line is a migration from the state below it to the one above. You never have to type a whole hash when you name one of these states, because commands accept any prefix of 6 or more characters that matches one hash. The labels on the right say what points at each state:
@contractis the contract you last emitted.@dbis the contract state the database's marker records.(db)is thedbref, a file inmigrations/app/refs/that names the contract state you last applied in development.
When migrations are pending, migration status also prints a db migrate --to <hash> command that you can copy, and if that hash is your @contract state you can leave --to off, because that is the state db migrate runs to anyway.
db migrate --show is the preview, and it changes nothing: it draws the same history, marks each migration it would apply with will run, and lists them in the order they would run:
○ 967cb9f @contract
│↑ 20260707T1006_add_user_phone 2d2cec8 → 967cb9f ↑ will run
○ 2d2cec8 @db (db)
│↑ 20260707T1005_init ∅ → 2d2cec8
○ ∅
ℹ The following 1 migration will run:
20260707T1006_add_user_phone 2d2cec8 → 967cb9f--show tells you which migrations will run, but not what is inside them, so when you want one migration's operations and its SQL, run npx prisma migration show 20260707T1006_add_user_phone instead, as Reviewing what you planned shows.
After applying, npx prisma migration log --db "$PRODUCTION_DATABASE_URL" shows you what that database has actually had applied to it, rather than what your local files say: it prints the ledger, the database's own list of every migration applied to it. Like --show, it changes nothing:
Applied at Migration Change Ops
2026-07-07 10:05:32 +00:00 20260707T1005_init ∅ → 2d2cec8 2 ops
2026-07-07 10:09:55 +00:00 20260707T1006_add_user_phone 2d2cec8 → 967cb9f 1 opsIn a pipeline, nobody reads the preview, so the job is step 3 alone, with --to <ref> so it applies only the migrations someone has pointed the ref at; Choosing a target explains refs. db migrate checks the marker before it runs anything and fails with MIGRATION.MARKER_MISMATCH when the marker names a state outside your history, and Drift says what to do then. Migrate a shared database from a workflow shows the job.
The examples in the rest of this section use a project with two migrations, init and add_user_phone, where the production database has applied only init.
npx prisma migration status --db "$PRODUCTION_DATABASE_URL" never changes the database, so you can run it against production whenever you want to know which contract state that database matches. It draws your migration history, marks each migration as applied or pending, and shows which contract state the database matches:
○ 967cb9f @contract
│↑ 20260707T1006_add_user_phone 2d2cec8 → 967cb9f 1 ops ⧗ pending
○ 2d2cec8 @db (db)
│↑ 20260707T1005_init ∅ → 2d2cec8 2 ops ✓ applied
○ ∅Read the diagram from the bottom up, because it starts with an empty database and ends with the contract you last emitted: ∅ is an empty database, each ○ line is a contract state shown by the first 7 characters of its hash, and each │↑ line is a migration from the state below it to the one above. You never have to type a whole hash when you name one of these states, because commands accept any prefix of 6 or more characters that matches one hash. The labels on the right say what points at each state:
@contractis the contract you last emitted.@dbis the contract state the database's marker records.(db)is thedbref, a file inmigrations/app/refs/that names the contract state you last applied in development.
When migrations are pending, migration status also prints a db migrate --to <hash> command that you can copy, and if that hash is your @contract state you can leave --to off, because that is the state db migrate runs to anyway.
db migrate --show is the preview, and it changes nothing: it draws the same history, marks each migration it would apply with will run, and lists them in the order they would run:
○ 967cb9f @contract
│↑ 20260707T1006_add_user_phone 2d2cec8 → 967cb9f ↑ will run
○ 2d2cec8 @db (db)
│↑ 20260707T1005_init ∅ → 2d2cec8
○ ∅
ℹ The following 1 migration will run:
20260707T1006_add_user_phone 2d2cec8 → 967cb9f--show tells you which migrations will run, but not what is inside them, so when you want one migration's operations and its SQL, run npx prisma migration show 20260707T1006_add_user_phone instead, as Reviewing what you planned shows.
After applying, npx prisma migration log --db "$PRODUCTION_DATABASE_URL" shows you what that database has actually had applied to it, rather than what your local files say: it prints the ledger, the database's own list of every migration applied to it. Like --show, it changes nothing:
Applied at Migration Change Ops
2026-07-07 10:05:32 +00:00 20260707T1005_init ∅ → 2d2cec8 2 ops
2026-07-07 10:09:55 +00:00 20260707T1006_add_user_phone 2d2cec8 → 967cb9f 1 opsIn a pipeline, nobody reads the preview, so the job is step 3 alone, with --to <ref> so it applies only the migrations someone has pointed the ref at; Choosing a target explains refs. db migrate checks the marker before it runs anything and fails with MIGRATION.MARKER_MISMATCH when the marker names a state outside your history, and Drift says what to do then. Migrate a shared database from a workflow shows the job.
By default db migrate runs everything that is pending, so when you want to stop part way, name the contract state you want to stop at with --to. The example below uses prod, a ref created with npx prisma migration ref set prod <dir>, which points prod at the state after that migration:
bunx prisma db migrate --to prod --db "$PRODUCTION_DATABASE_URL"pnpm prisma db migrate --to prod --db "$PRODUCTION_DATABASE_URL"yarn prisma db migrate --to prod --db "$PRODUCTION_DATABASE_URL"npx prisma db migrate --to prod --db "$PRODUCTION_DATABASE_URL"When the state you want is an earlier one than the database already matches, plan a rollback migration first, as Rollbacks and recovery shows, because db migrate only ever runs migrations you already have. That is also why, if no migrations lead from the state the database matches to the one you name, the run fails with an error whose code is MIGRATION.PATH_UNREACHABLE. The db migrate reference lists every form of --to and the other options.
When the state you want is an earlier one than the database already matches, plan a rollback migration first, as Rollbacks and recovery shows, because db migrate only ever runs migrations you already have. That is also why, if no migrations lead from the state the database matches to the one you name, the run fails with an error whose code is MIGRATION.PATH_UNREACHABLE. The db migrate reference lists every form of --to and the other options.
When an operation fails, db migrate stops and prints what failed and why:
✘ [MIGRATION.RUNNER_FAILED] Operation pgvector.install-vector-extension failed during execution: create extension "vector"
why: extension "vector" is not available
docs: https://docs.prisma.io/docs/orm/v8/reference/error-reference/MIGRATION.RUNNER_FAILEDHere, an operation from the pgvector extension package failed because the vector PostgreSQL extension is not available on the database server. Install it there, or enable it in your database host's settings, and then run db migrate again.
On PostgreSQL you can do that without any tidying up first, because a failed run leaves no changes in the database at all. The whole run is one transaction, so everything the failed run did is rolled back, while migrations applied in earlier runs stay applied. The error itself tells you where it stopped, because it names the operation and the failing step, which is either the SQL statement or a check before or after it, such as the check that no row still holds NULL before a column becomes NOT NULL. And when you fix the problem and run the command again, it does not repeat work that is already done, because db migrate skips each operation whose change is already in the database, unless the operation has no postcheck.
MongoDB does not give you that clean slate, because a run there is not one transaction. Operations that finished before the failure stay in the database, and a re-run stops at the precheck of any collection the failed run already created.
db migrate also refuses to start when the marker records a contract state that none of your migrations starts or ends at, because it cannot then work out which migrations to run. It fails with an error whose code is MIGRATION.MARKER_MISMATCH, and no operation runs. You see this when the database's contract state was set by something other than the migrations you have on disk, which happens in the following situations:
npx prisma db update, which replacesprisma db pushfor local development and preview environments. Plan your next migration with--fromso that it starts at the migration you last applied, as Drift shows.- A migration from a Git branch you have not merged, which a shared database such as staging already applied. Merge that branch so that you have the migration too, and if your branch also added migrations, plan one migration from the state that database matches, as A worked example shows.
A database that already had tables gets its marker from npx prisma db sign, so db migrate refuses to run against it until a migration in your history ends at the signed state, as The automatic baseline shows. For a database Prisma ORM 7 migrated, follow Transfer migration ownership in the upgrade guide.
The command is the same everywhere, and what changes is the work you do around it.
In development, you emit the contract, plan a migration, review it, and then apply it:
bunx prisma contract emit
bunx prisma migration plan --name my_change
# Review the new migration directory before you apply it.
bunx prisma db migrate --advance-ref dbpnpm prisma contract emit
pnpm prisma migration plan --name my_change
# Review the new migration directory before you apply it.
pnpm prisma db migrate --advance-ref dbyarn prisma contract emit
yarn prisma migration plan --name my_change
# Review the new migration directory before you apply it.
yarn prisma db migrate --advance-ref dbnpx prisma contract emit
npx prisma migration plan --name my_change
# Review the new migration directory before you apply it.
npx prisma db migrate --advance-ref dbIn development, add --advance-ref db so the next migration plan starts from what you just applied, and leave the flag off in CI and production, where you never plan a migration anyway. The db ref covers what happens without it and how to fix a missed flag. If you edit migration.ts while reviewing the migration, recompile it with node migrations/app/<dir>/migration.ts before you apply, as Editing a migration explains.
In CI and production, you never plan a migration, because migrations arrive through your repository, already reviewed and merged. For that to work, commit the whole migrations/ directory and your contract files, which means:
- each migration's directory under
migrations/app/ - the refs in
migrations/app/refs/, such asprod.json, whichdb migrate --to prodreads - the copies of your contract that
migration plansaved undermigrations/snapshots/, one directory per contract state - one directory per extension package that ships migrations, such as
migrations/pgvector/ contract.prisma,contract.json, andcontract.d.ts
When you deploy by hand, run the three commands in Check before, preview, then apply. The point of the migration check step is to tell you that what you are about to apply is still what was reviewed. In a pipeline, the deploy job runs only the last of them, db migrate with --to <ref>, which that section explains. That one command is all the pipeline needs, because db migrate refuses to run when a migration's ops.json or migration.json no longer matches the hash recorded for it.
Each migration's migration.json records a hash, migrationHash, computed from the rest of migration.json and from ops.json. If either file changes after the hash was recorded, for example because you edited it by hand, migration check fails with an error whose code is MIGRATION.CHECK_HASH_MISMATCH, as Editing a migration explains. To fix it, either restore the migration's files from Git, or move the hand edit into migration.ts and recompile with node migrations/app/<dir>/migration.ts.
One case needs a different fix. If the error comes back right after you recompile, and the migration's backfill updates a model with a temporal.updatedAtString() column, recompiling cannot fix it. Editing a migration shows how to write that backfill instead.
When a contract snapshot under migrations/snapshots/ no longer matches the hash in its directory name, migration check fails with an error whose code is MIGRATION.CHECK_SNAPSHOT_CONTENT_MISMATCH. Other commands that read that snapshot, such as migration plan --from <that state> or migration ref set prod <that state>, fail with a different code, MIGRATION.CONTRACT_SNAPSHOT_CONTENT_MISMATCH. Recompiling migration.ts fixes neither error, so restore the edited snapshot from Git. MongoDB snapshots are not checked yet.
If two deployments can run at once, what happens next depends on your database. On PostgreSQL, when two db migrate runs start against the same database, one of them waits for the other. On MongoDB, if another run updates the marker while yours is running, yours fails with Marker was modified by another process during migration execution. When that happens, run npx prisma migration status to see which contract state the database matches, and then run db migrate again.
In development, add --advance-ref db so the next migration plan starts from what you just applied, and leave the flag off in CI and production, where you never plan a migration anyway. The db ref covers what happens without it and how to fix a missed flag. If you edit migration.ts while reviewing the migration, recompile it with node migrations/app/<dir>/migration.ts before you apply, as Editing a migration explains.
In CI and production, you never plan a migration, because migrations arrive through your repository, already reviewed and merged. For that to work, commit the whole migrations/ directory and your contract files, which means:
- each migration's directory under
migrations/app/ - the refs in
migrations/app/refs/, such asprod.json, whichdb migrate --to prodreads - the copies of your contract that
migration plansaved undermigrations/snapshots/, one directory per contract state - one directory per extension package that ships migrations, such as
migrations/pgvector/ contract.prisma,contract.json, andcontract.d.ts
When you deploy by hand, run the three commands in Check before, preview, then apply. The point of the migration check step is to tell you that what you are about to apply is still what was reviewed. In a pipeline, the deploy job runs only the last of them, db migrate with --to <ref>, which that section explains. That one command is all the pipeline needs, because db migrate refuses to run when a migration's ops.json or migration.json no longer matches the hash recorded for it.
Each migration's migration.json records a hash, migrationHash, computed from the rest of migration.json and from ops.json. If either file changes after the hash was recorded, for example because you edited it by hand, migration check fails with an error whose code is MIGRATION.CHECK_HASH_MISMATCH, as Editing a migration explains. To fix it, either restore the migration's files from Git, or move the hand edit into migration.ts and recompile with node migrations/app/<dir>/migration.ts.
One case needs a different fix. If the error comes back right after you recompile, and the migration's backfill updates a model with a temporal.updatedAtString() column, recompiling cannot fix it. Editing a migration shows how to write that backfill instead.
When a contract snapshot under migrations/snapshots/ no longer matches the hash in its directory name, migration check fails with an error whose code is MIGRATION.CHECK_SNAPSHOT_CONTENT_MISMATCH. Other commands that read that snapshot, such as migration plan --from <that state> or migration ref set prod <that state>, fail with a different code, MIGRATION.CONTRACT_SNAPSHOT_CONTENT_MISMATCH. Recompiling migration.ts fixes neither error, so restore the edited snapshot from Git. MongoDB snapshots are not checked yet.
If two deployments can run at once, what happens next depends on your database. On PostgreSQL, when two db migrate runs start against the same database, one of them waits for the other. On MongoDB, if another run updates the marker while yours is running, yours fails with Marker was modified by another process during migration execution. When that happens, run npx prisma migration status to see which contract state the database matches, and then run db migrate again.
If your project uses a Prisma ORM extension package, such as pgvector support, the output shows more than one contract space, which is a separate migration history: your app has one, and so does each extension package that ships migrations. migration plan writes an extension's migrations to migrations/<space>/, such as migrations/pgvector/, and you have to commit that directory, because db migrate fails without it. A single run applies every space and reports each one separately, while --to applies only to your app. What an extension space ends up matching is decided by what you committed rather than by what is in node_modules, because db migrate applies the migrations in that committed directory until the database matches the newest state there, not the package version you have installed:
✔ Applied 2 migration(s) (20 operation(s)) across 2 contract space(s)
Extension space: pgvector
├─ Enable extension "vector"
└─ marker 3d2c56a2944685bd21b05bc8a8d73164397df51c014201902932fbe7e80ff1b8
App space
├─ Create table "user"
└─ ...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:
- "Check migration status against staging and apply whatever is pending."
- "List what
db migrate --to prodwould run, then runmigration showon each migration and summarize the destructive operations." - "Apply the pending migrations and advance the
dbref."
- Rollbacks and recovery: when the database needs an earlier contract state
- The migration graph: markers, refs, and how migrations connect
- Generating a migration: producing what
db migrateruns - Studio with Prisma ORM: read the ledger as a visual timeline, with the executed SQL and a schema diff per migration