# How migrations work (/docs/orm/next/migrations/how-migrations-work)

Location: ORM > Next > Migrations > How migrations work

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:

<ConceptAnimation name="migration-loop" />

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](/next/quickstart/postgresql) gives you one), the whole loop is three commands. Add an optional `phone String?` field to a model in your `.prisma` file, then:

```bash
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:

```text
✔ 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:

```text
✔ 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 [#what-a-migration-contains]

A migration is a directory under `migrations/app/`, named with a timestamp and a slug:

```text
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:

| 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 [#migrationts-the-file-you-edit]

`migration.ts` is the authoring file. It contains the migration steps as ordinary TypeScript:

```ts
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](/orm/next/migrations/editing-a-migration).

ops.json: the file Prisma runs [#opsjson-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.

```text
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`:

```text
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 [#migrationjson-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

```json title="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](/orm/next/migrations/the-migration-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:

```text
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 [#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.

```json title="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 [#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](/orm/next/migrations/generating-a-migration) from your contract changes                               |
| `migration new`           | Scaffold an empty migration for [manual authoring](/orm/next/migrations/editing-a-migration#starting-from-a-blank-migration) |
| `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](/orm/next/migrations/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](/orm/next/migrations/applying-a-migration)    |
| `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:

<video controls muted playsInline preload="metadata" src="/docs/img/orm/next/migrations/migration-loop.mp4" aria-label="A 30-second walkthrough: a feature idea becomes a contract change, the migration is planned, reviewed, and applied, the new column appears in Prisma Studio, and application code autocompletes against the new schema." style={{ width: "100%", borderRadius: "0.75rem", border: "1px solid var(--color-fd-border)" }} />

The same model for SQL and MongoDB [#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.

> [!NOTE]
> Migrations are early
> 
> 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 [#prompt-your-coding-agent]

Projects scaffolded with `create-prisma@next` install [Prisma Next skills](/ai/tools/skills#available-skills-for-prisma-next) 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 [#see-also]

* [The migration graph](/orm/next/migrations/the-migration-graph): why migrations form a graph and what that buys you
* [Generating a migration](/orm/next/migrations/generating-a-migration) and [Applying a migration](/orm/next/migrations/applying-a-migration): the hands-on loop
* [Editing a migration](/orm/next/migrations/editing-a-migration): backfills, raw SQL, and the recompile step
* [Rethinking Database Migrations](https://www.prisma.io/blog/rethinking-database-migrations): the blog post on why this design exists

## Related pages

- [`Applying a migration`](https://www.prisma.io/docs/orm/next/migrations/applying-a-migration): prisma-next migrate walks the graph from where your database is to where you want it, with a preview, a checkpoint at every step, and safe retries.
- [`Editing a migration`](https://www.prisma.io/docs/orm/next/migrations/editing-a-migration): A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL, then recompile it with one command.
- [`Generating a migration`](https://www.prisma.io/docs/orm/next/migrations/generating-a-migration): Turn a contract change into a reviewable migration with prisma-next migration plan.
- [`Rollbacks and recovery`](https://www.prisma.io/docs/orm/next/migrations/rollbacks-and-recovery): Rolling back is planning one more migration to a state you've already been in. Recovery is fixing the cause and re-running, safely.
- [`The migration graph`](https://www.prisma.io/docs/orm/next/migrations/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.