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

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

Change your contract, plan a migration, review it, apply it. Every step is checked before and after it runs.

Location: ORM > Migrations > How migrations work

A migration is how Prisma 8 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 `contract emit`, which compiles the contract into the `contract.json` artifact that every other command reads.
2. **Plan a migration**: `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**: `db migrate` runs the pending migrations against your database.

In a project with a database connection configured (the [quickstart](https://www.prisma.io/docs/prisma-orm/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:

  

#### bun

```bash
bunx prisma@latest contract emit
bunx prisma@latest migration plan --name add_user_phone
bunx prisma@latest db migrate
```

#### pnpm

```bash
pnpm dlx prisma@latest contract emit
pnpm dlx prisma@latest migration plan --name add_user_phone
pnpm dlx prisma@latest db migrate
```

#### yarn

```bash
yarn dlx prisma@latest contract emit
yarn dlx prisma@latest migration plan --name add_user_phone
yarn dlx prisma@latest db migrate
```

#### npm

```bash
npx prisma@latest contract emit
npx prisma@latest migration plan --name add_user_phone
npx prisma@latest db 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 `db 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. You can 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](https://www.prisma.io/docs/orm/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, Prisma turns the TypeScript steps into JSON operations. The planner runs it for you. After an edit, re-run it yourself with `node migration.ts`. 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](https://www.prisma.io/docs/orm/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 8 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 and the database returns to exactly where it was. Fix the cause and re-run. 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]

These commands live under the [Prisma 8 CLI](https://www.prisma.io/docs/cli). Run them with `npx prisma@latest <command>`. The planning and inspection commands work offline. They read files, not your database:

| Command                   | What it does                                                                                                            |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `migration plan`          | [Generate a migration](https://www.prisma.io/docs/orm/migrations/generating-a-migration) from your contract changes                               |
| `migration new`           | Scaffold an empty migration for [manual authoring](https://www.prisma.io/docs/orm/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](https://www.prisma.io/docs/orm/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                                                             |
| ------------------ | ------------------------------------------------------------------------ |
| `db migrate`       | [Apply pending migrations](https://www.prisma.io/docs/orm/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 `db migrate`, not `migration apply`: `migration ...` commands manage the on-disk migration directories, while `db 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/v8/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 8 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
> 
> Migrations are one of the newest parts of Prisma 8. The core loop on this page (plan, edit, apply, roll back) works today and is exercised in the Prisma 8 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@latest` install [Prisma 8 skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) 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]

* [Studio with Prisma 8](https://www.prisma.io/docs/studio/prisma-next): read your applied migration history in Prisma Studio: a visual diff, the executed SQL, and a schema diff per migration
* [The migration graph](https://www.prisma.io/docs/orm/migrations/the-migration-graph): why migrations form a graph and what that buys you
* [Generating a migration](https://www.prisma.io/docs/orm/migrations/generating-a-migration) and [Applying a migration](https://www.prisma.io/docs/orm/migrations/applying-a-migration): the hands-on loop
* [Editing a migration](https://www.prisma.io/docs/orm/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/migrations/applying-a-migration): The db migrate command 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/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/migrations/generating-a-migration): Turn a contract change into a reviewable migration with the migration plan command.
- [`Rollbacks and recovery`](https://www.prisma.io/docs/orm/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/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.