Prisma Next is in early access.Read the docs

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:

The migration loopStep 1 of 4
next changeChange the contractyour schemaPlanwrites migration filesReviewread it, edit if neededApplyruns against the database
Edit your schema, then emit it. The contract is what every migration command reads.
  1. Change your contract: edit your .prisma file, then run prisma-next contract emit, which compiles the contract into the contract.json artifact that every other command reads.
  2. Plan a migration: prisma-next migration plan compares the new contract against your migration history and writes a migration directory under migrations/.
  3. 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.
  4. Apply it: prisma-next migrate runs 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 migrate

migration 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.ts

Three files matter day to day. Each has one job:

FilePurpose
migration.tsThe file you edit. It describes the schema and data changes as TypeScript function calls.
ops.jsonThe file Prisma runs. It contains the compiled migration operations as JSON.
migration.jsonThe 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 runs

The 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 run

migration.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
migrations/app/20260707T1006_add_user_phone/migration.json
{
  "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:

  1. Precheck: confirms the database is in the expected state before the change runs.
  2. Execute: the statements that make the change.
  3. 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.
One operation from ops.json
{
  "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:

CommandWhat it does
migration planGenerate a migration from your contract changes
migration newScaffold an empty migration for manual authoring
migration show <target>Print one migration's operations, DDL preview, and metadata
migration listList every on-disk migration
migration graphDraw the migration graph
migration checkVerify migration files and graph integrity (useful in CI)

Three commands talk to a database:

CommandWhat it does
migrateApply pending migrations
migration statusShow where the database is in the graph and which migrations are pending
migration logShow 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.

Prompt your coding agent

Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent. Ask your agent to:

  • "Add a phone field 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

On this page