How migrations work
Change your contract, plan a migration, review it, apply it. Every step is checked before and after it runs.
A migration is how Prisma Next changes your database when your contract changes. The contract is the schema description you author (a .prisma file or TypeScript) plus the contract.json artifact it compiles to; the schema is the database's actual structure, and migrations are what move it. You add a field to a model, and something has to run ALTER TABLE against every database that serves your app: your laptop, staging, production. Migrations are that something, recorded as files in your repo so the change is reviewable, repeatable, and versioned with your code.
The workflow is a loop you will run many times a day in development:
- Change your contract: edit your
.prismafile, then runprisma-next contract emit, which compiles the contract into thecontract.jsonartifact that every other command reads. - Plan a migration:
prisma-next migration plancompares the new contract against your migration history and writes a migration directory undermigrations/. - Review it: read the generated TypeScript and the DDL preview. Edit the migration if the change needs a data step, then re-run the file to recompile it.
- Apply it:
prisma-next migrateruns the pending migrations against your database.
In a project with a database connection configured (the quickstart gives you one), the whole loop is three commands. Add an optional phone String? field to a model in your .prisma file, then:
npx prisma-next contract emit
npx prisma-next migration plan --name add_user_phone
npx prisma-next migratemigration plan prepares the SQL for the migration, but doesn't run it:
✔ Planned 1 operation(s)
│
└─ Add column "phone" to "user"
DDL preview
ALTER TABLE "public"."user" ADD COLUMN "phone" text;and migrate applies the migration to your database and confirms what it applied:
✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)The contract-space count only matters once you add database extensions; ignore it for now. If you see the ✔ Applied line, you've already run the entire workflow. The rest of this page covers additional detail: what a migration is, and why it looks the way it does.
What a migration contains
A migration is a directory under migrations/app/, named with a timestamp and a slug:
migrations/
└── app/
└── 20260707T1006_add_user_phone/
├── migration.ts # the file you edit
├── ops.json # the file Prisma runs
├── migration.json # where this migration fits in history
├── start-contract.json # snapshot of the contract before
├── end-contract.json # snapshot of the contract after
└── *-contract.d.ts # types for those snapshots; they type-check migration.tsThree files matter day to day. Each has one job:
| File | Purpose |
|---|---|
migration.ts | The file you edit. It describes the schema and data changes as TypeScript function calls. |
ops.json | The file Prisma runs. It contains the compiled migration operations as JSON. |
migration.json | The file Prisma uses to track where this migration fits in history. |
migration.ts: the file you edit
migration.ts is the authoring file. It contains the migration steps as ordinary TypeScript:
this.addColumn(...)
this.createTable(...)
this.dataTransform(...)Because it is TypeScript, type-checked against the contract snapshots sitting next to it, your editor autocompletes table and column names and catches mistakes before the migration touches a database.
Use this file when you want to review or change what the migration does. See Editing a migration.
ops.json: the file Prisma runs
ops.json is the compiled form of the migration. When migration.ts runs (the planner does this for you; after an edit, re-run it with node migration.ts), Prisma turns the TypeScript steps into JSON operations. The migration runner reads only this file.
That means production never executes your TypeScript. It reads the compiled operations, so no application code runs with production credentials.
migration.ts -> ops.json
you edit Prisma runsThe two files are committed side by side and work like package.json and package-lock.json:
package.json -> package-lock.json
what you ask for exact resolved result
migration.ts -> ops.json
what you author exact operations to runmigration.json: the history marker
migration.json records where this migration fits in your database history. Every contract compiles to a deterministic artifact, and hashing it gives a short identifier for that exact schema state. A migration records:
- the schema hash it starts
from - the schema hash it moves
to - when it was created
- its own hash, so tampering is detectable
{
"from": "sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5",
"to": "sha256:925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72",
"providedInvariants": [],
"createdAt": "2026-07-07T10:06:55.937Z",
"migrationHash": "sha256:4b57fa2141c8ad94476d5de66c451468fd6c864210481ad00ac89b259491fbcf"
}Read from and to like a Git commit range: the schema state before, and the schema state after. These hashes are what link migrations into a graph instead of a numbered list. If you want a simple linear migration system, you can ignore the graph entirely.
Together, the three files make a migration both editable and safe to run:
migration.ts you author the change
ops.json Prisma runs the compiled operations
migration.json Prisma tracks the migration in history(providedInvariants is used by extension migrations and stays empty for typical app migrations.)
Every operation checks itself
Inside ops.json, each operation runs in three parts, in order:
- Precheck: confirms the database is in the expected state before the change runs.
- Execute: the statements that make the change.
- Postcheck: confirms the change worked after it runs.
Each operation also carries a class, which drives the (destructive) flags and data-loss warnings you see in CLI output:
- Additive: safe to apply, like adding a column.
- Destructive: can lose data, like dropping a column.
- Data: changes rows, not structure.
{
"id": "column.public.user.phone",
"label": "Add column \"phone\" to \"user\"",
"operationClass": "additive",
"precheck": [
{
"description": "ensure column \"phone\" is missing",
"sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"information_schema\".\"columns\" WHERE (\"table_schema\" = $1 AND \"table_name\" = $2 AND \"column_name\" = $3)) AS \"result\"",
"params": ["public", "user", "phone"]
}
],
"execute": [
{
"description": "add column \"phone\"",
"sql": "ALTER TABLE \"public\".\"user\" ADD COLUMN \"phone\" text"
}
],
"postcheck": [
{
"description": "verify column \"phone\" exists",
"sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"information_schema\".\"columns\" WHERE (\"table_schema\" = $1 AND \"table_name\" = $2 AND \"column_name\" = $3)) AS \"result\"",
"params": ["public", "user", "phone"]
}
]
}This structure is what makes Prisma Next migrations safe in the situations where classic SQL migrations hurt:
- A migration fails. On PostgreSQL the whole run is one transaction, so a failure rolls everything back: the database returns to exactly where it was while you fix the cause and re-run. And because the runner skips any operation whose postcheck already holds, re-running never double-applies work that's already true in the database. No commenting out statements, no hand-editing the database.
- The database isn't in the state you assumed. The precheck catches it before the change runs, and the error names the exact operation and the exact check that failed, not a generic SQL error halfway through.
- Someone (or some agent) wrote the migration for you. The intent (
migration.ts), the exact SQL (ops.json), and the verification for every step are all in the diff, so review is straightforward.
The commands
Everything lives under the prisma-next CLI. Planning and inspection are offline; they read files, not your database:
| Command | What it does |
|---|---|
migration plan | Generate a migration from your contract changes |
migration new | Scaffold an empty migration for manual authoring |
migration show <target> | Print one migration's operations, DDL preview, and metadata |
migration list | List every on-disk migration |
migration graph | Draw the migration graph |
migration check | Verify migration files and graph integrity (useful in CI) |
Three commands talk to a database:
| Command | What it does |
|---|---|
migrate | Apply pending migrations |
migration status | Show where the database is in the graph and which migrations are pending |
migration log | Show the history of migrations the database has actually applied |
Note that applying is prisma-next migrate, not migration apply: migration ... commands manage the on-disk migration directories, while migrate moves a database.
Here is the whole loop in motion, from idea to typed code against the new schema:
The same model for SQL and MongoDB
Everything on this page applies to both database families. On PostgreSQL, operations compile to SQL DDL and the applied state is tracked in a marker, a record Prisma Next keeps in the database itself naming the contract state the database currently matches. On MongoDB, operations create collections, indexes, and JSON Schema validators, and the marker lives in a _prisma_migrations collection. The commands, the file layout, the graph, and the precheck/execute/postcheck structure are identical.
Prisma Next is in Early Access and migrations are one of its newest parts. The core loop on this page (plan, edit, apply, roll back) works today and is exercised in the Prisma Next test suite. Some things classic migration tools grew over years are not built yet: there are no squash/baseline commands, and no shadow-database dry run. We call out what's missing on each page rather than papering over it.
Prompt your coding agent
Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent. Ask your agent to:
- "Add a
phonefield to the User model and plan a migration for it." - "Show me the pending migrations and what SQL they will run."
- "Explain what the ops.json in the latest migration does."
See also
- The migration graph: why migrations form a graph and what that buys you
- Generating a migration and Applying a migration: the hands-on loop
- Editing a migration: backfills, raw SQL, and the recompile step
- Rethinking Database Migrations: the blog post on why this design exists
Advanced queries
Use the SQL query builder on PostgreSQL and the pipeline builder on MongoDB for queries the ORM API can't express.
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.