# Editing a migration (/docs/orm/migrations/editing-a-migration)

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

A migration is TypeScript you own. Fill in backfills, reorder steps, or write raw SQL, then recompile it with one command.

Location: ORM > Migrations > Editing a migration

Each of your app's migrations is a directory in `migrations/app/`, and the file you edit inside it is `migration.ts`, a TypeScript file that describes the change as a list of calls such as `this.addColumn(...)`. `npx prisma migration plan` writes the first draft of that file for you, but it can only work out the parts that follow from your contract. The decisions that depend on your data are left to you: what value existing rows should get, which order the steps should run in, and how to write a statement that has no method of its own.

The file that actually runs against your database is not the one you edit. `npx prisma db migrate`, which replaces [`prisma migrate deploy`](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7), runs the SQL in each migration's `ops.json`, and fixing that SQL by hand does not work, because Prisma ORM notices: `npx prisma migration check` fails and `npx prisma db migrate` refuses to run. Edit `migration.ts` instead and then recompile it, which means running the file itself with Node.js from your project root. Recompiling rewrites both `ops.json` and `migration.json` for you, and never connects to a database:

```bash
node migrations/app/20260707T1008_add_display_name/migration.ts
```

```text
Wrote ops.json + migration.json to /path/to/my-app/migrations/app/20260707T1008_add_display_name
```

You can run the file like this because the Prisma ORM CLI already needs Node.js 22.18 or later, and those versions run `migration.ts` directly, with no build step. Run it from your project root: if you change into the migration directory first and run it from there, the command fails with an error whose `code` is `CONFIG.FILE_NOT_FOUND`.

The other file recompiling rewrites, `migration.json`, is what makes a hand-edit detectable. It records the contract state the migration starts `from`, the state it ends at, `to`, and a hash of the migration. `npx prisma migration check` recomputes that hash from `ops.json` and `migration.json` and compares it with the stored one, so any change to either file that did not come from a recompile no longer matches. The check never reads `migration.ts`, which is why an edit you make there counts for nothing until you recompile.

Editing a migration only changes what happens on databases that have not applied it yet, because a database that already applied it will not apply it again. When you need to change a database that has already applied the migration, plan a new migration for the change instead. When you only want to try your edit against a development database that already applied it, drop and recreate that database with your own tools, because Prisma ORM 8 has no replacement for `prisma migrate reset`. Then run `npx prisma db migrate --advance-ref db`, which points the [`db` ref](https://www.prisma.io/docs/orm/migrations/generating-a-migration#the-db-ref-skipping---from) at the state you just applied. The `db` ref is the name Prisma ORM keeps for the contract state you last applied in development, and it lives in a file under `migrations/app/refs/`.

## Worked example: making a column required [#worked-example-making-a-column-required]

Say you add a required `displayName` field to `User` in your contract, the `contract.prisma` file that replaced `schema.prisma`. The rows already in your `user` table have no `displayName`, so you have to decide what those rows get. Start by running `npx prisma contract emit`, which replaces `prisma generate` and writes `contract.json` and `contract.d.ts`, and then plan the migration as [Generating a migration](https://www.prisma.io/docs/orm/migrations/generating-a-migration#the-db-ref-skipping---from) describes:

  

#### bun

```bash
bunx prisma migration plan --name add_display_name
```

#### pnpm

```bash
pnpm dlx prisma migration plan --name add_display_name
```

#### yarn

```bash
yarn dlx prisma migration plan --name add_display_name
```

#### npm

```bash
npx prisma migration plan --name add_display_name
```

```text
⚠ Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit

from:       925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72
to:         e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63
app space:  migrations/app/20260707T1008_add_display_name
```

In that output, `self-emit` is what the CLI calls recompiling, and `app space` is the directory the new migration was written to. `migration plan` writes the change as three steps rather than one, because the column is required and the table already has rows: `addColumn` adds the column as nullable, `dataTransform` holds the backfill you write, and `setNotNull` makes the column required. The table is called `user` because the table name is the model name with a lowercase first letter, unless the model sets `@@map`:

```ts title="migration.ts (as planned)"
override get operations() {
  return [
    this.addColumn({
      schema: 'public',
      table: 'user',
      column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }),
    }),
    this.dataTransform(endContract, 'backfill-user-displayName', {
      check: () => placeholder('backfill-user-displayName:check'),
      run: () => placeholder('backfill-user-displayName:run'),
    }),
    this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }),
  ];
}
```

A `dataTransform` is where you say what should happen to the rows already in the table, and it takes two callbacks. Each one returns a query built with the [`sql` query builder](https://www.prisma.io/docs/orm/reference/sql-query-builder), not a SQL string. `check` returns the rows that still need the backfill, which is how Prisma ORM tells whether there is any work left to do, and `run` is the query that changes them. Inside each `where`, `f` holds the table's columns and `fns` holds [functions](https://www.prisma.io/docs/orm/reference/sql-query-builder#expressions-and-functions) such as `eq`, which is why `fns.eq(f.displayName, null)` becomes `IS NULL`. When you want to set a column from another column, pass [`update()`](https://www.prisma.io/docs/orm/reference/sql-query-builder#update) a callback instead of a plain object: `update((f) => ({ displayName: f.email }))`. The imports at the top of the file below come from snapshots, which are copies of your contract that `migration plan` saves in `migrations/snapshots/<hash>/`, and this migration imports the snapshot from before it and the one from after it, so the queries you write are typed against the contract you mean.

```ts title="migration.ts (filled in)"
#!/usr/bin/env -S node
import type { Contract as End } from '../../snapshots/e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63/contract';
import endContractJson from '../../snapshots/e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63/contract.json' with { type: 'json' };
import type { Contract as Start } from '../../snapshots/925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72/contract';
import startContractJson from '../../snapshots/925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72/contract.json' with { type: 'json' };
import { Migration, MigrationCLI, col } from '@prisma/orm-postgres/migration';
import postgresAdapter from '@prisma/orm-postgres/adapter/runtime';
import { sql } from '@prisma/orm-postgres/builder/runtime';
import { createExecutionContext, createSqlExecutionStack } from '@prisma/orm-postgres/family-runtime';
import postgresTarget, { PostgresContractSerializer } from '@prisma/orm-postgres/target/runtime';

const endContract = new PostgresContractSerializer().deserializeContract<End>(endContractJson);
const stack = createSqlExecutionStack({ target: postgresTarget, adapter: postgresAdapter });

const db = sql<End>({
  context: createExecutionContext({ contract: endContract, stack }),
  rawCodecInferer: stack.adapter.rawCodecInferer,
});

export default class M extends Migration<Start, End> {
  override readonly endContractJson = endContractJson;
  override readonly startContractJson = startContractJson;

  override get operations() {
    return [
      this.addColumn({
        schema: 'public',
        table: 'user',
        column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }),
      }),
      this.dataTransform(endContract, 'backfill-user-displayName', {
        check: () =>
          db.public.user
            .select('id')
            .where((f, fns) => fns.eq(f.displayName, null))
            .limit(1),
        run: () =>
          db.public.user
            .update({ displayName: 'Anonymous' })
            .where((f, fns) => fns.eq(f.displayName, null)),
      }),
      this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }),
    ];
  }
}

MigrationCLI.run(import.meta.url, M);
```

If you compare that with the file `migration plan` wrote, the differences are all things you add or rename. The planned file's migration import also brings in `placeholder`, and it does not have the four other `@prisma/orm-postgres` imports or the `endContract`, `stack`, and `db` statements, which only build queries and never connect to a database, so you write those in yourself. The planned file also names its snapshot `contract.json` imports `endContract` and `startContract`, renamed here to `endContractJson` and `startContractJson` so that the name `endContract` is free for the deserialized contract. The only other thing you touch is the `placeholder(...)` calls, which become your `check` and `run` queries. The rest of each operation, such as `codecRef`, stays exactly as `migration plan` wrote it.

Now recompile the file with `node migrations/app/20260707T1008_add_display_name/migration.ts`. If you left a `placeholder(...)` call in it, recompiling fails with an error whose `code` is `MIGRATION.UNFILLED_PLACEHOLDER`, and `npx prisma db migrate` fails as well, because `ops.json` is still empty. Once it recompiles, read the `UPDATE` it produced in `ops.json` and check that it sets the value you meant on the rows you meant:

```json title="ops.json (the compiled dataTransform)"
{
  "id": "data_migration.backfill-user-displayName",
  "label": "Data transform: backfill-user-displayName",
  "operationClass": "data",
  "target": { "id": "postgres" },
  "precheck": [
    {
      "description": "Check backfill-user-displayName has work to do",
      "sql": "SELECT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok",
      "params": []
    }
  ],
  "execute": [
    {
      "description": "Run backfill-user-displayName",
      "sql": "UPDATE \"public\".\"user\" SET \"displayName\" = $1 WHERE \"displayName\" IS NULL",
      "params": ["Anonymous"]
    }
  ],
  "postcheck": [
    {
      "description": "Verify backfill-user-displayName resolved all violations",
      "sql": "SELECT NOT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok",
      "params": []
    }
  ]
}
```

There is one case where a correct-looking backfill still leaves you stuck, and it involves timestamps that Prisma ORM fills in for you. A column declared as `temporal.updatedAtString()` gets the current time whenever a row is created or updated, the way `@updatedAt` did in Prisma ORM 7. The `User` in this example has no such column, but the starter contract from `npx prisma orm init` does have one, called `updatedAt`. On a model like that, your backfill's `update` also sets `updatedAt`, and that is enough to make `npx prisma migration check` fail right after every recompile with an error whose `code` is `MIGRATION.CHECK_HASH_MISMATCH`, so `npx prisma db migrate` refuses to run.

The way out is to write the backfill against a contract of your own rather than the real one. Build `db` from an intermediate contract and pass that same contract to `dataTransform`, which the next section works through in full. In that contract, make `displayName` optional and declare `updatedAt` as `TimestamptzString`, a type Prisma ORM does not set for you, so the backfill leaves that column alone.

## When a backfill reads a column the migration removes [#when-a-backfill-reads-a-column-the-migration-removes]

Sooner or later a backfill has to read a column that the same migration takes away. Suppose the next migration replaces the boolean `isAdmin` on `User` with a required text column, `role`. Change your contract, run `npx prisma contract emit`, and plan the migration as in the `displayName` example. The backfill reads `isAdmin` to decide each row's `role`, and that is where the trouble starts, because the end contract has no `isAdmin` and the start contract has no `role`. If you build `db` from the end contract, TypeScript rejects `f.isAdmin`, and recompiling fails with an error that does not name the column, so the cause is hard to spot.

What you need instead is a contract that describes the database as it is while the backfill runs, and you write that one yourself. Copy your whole contract into the migration directory as `intermediate.prisma`, then in its `User` model add `isAdmin` back and make `role` optional, because at that point no row has a `role` yet.

```prisma title="intermediate.prisma (User, other fields left out)"
model User {
  id          Int     @id
  displayName String
  isAdmin     Boolean
  role        String?
}
```

In the same migration directory, add `intermediate.config.ts`, a second config file that names `intermediate.prisma` as the contract and needs no database connection:

```ts title="intermediate.config.ts"
import { definePrismaConfig } from 'prisma/config';
import { defineConfig as ormConfig } from '@prisma/orm-postgres/config';
export default definePrismaConfig({ orm: ormConfig({ contract: './intermediate.prisma' }) });
```

Emit the intermediate contract from inside the migration directory, because `intermediate.config.ts` reads its paths from the directory you run the command in. Commit all four `intermediate.*` files along with the migration, since the migration no longer recompiles without them.

```bash
cd migrations/app/20260708T0900_replace_is_admin
npx prisma contract emit --config intermediate.config.ts
cd ../../..
```

```text title="migrations/app/20260708T0900_replace_is_admin/"
├── intermediate.config.ts
├── intermediate.d.ts
├── intermediate.json
├── intermediate.prisma
├── migration.json
├── migration.ts
└── ops.json
```

Now fill in this migration's own planned `migration.ts`, rather than copying the `displayName` file, whose snapshot imports point at other contracts. The listing below builds `db` from the intermediate contract and passes that same contract to `dataTransform`, and those two have to be the same contract: if you pass a different one, `node migration.ts` fails with an error whose `code` is `MIGRATION.DATA_TRANSFORM_CONTRACT_MISMATCH`. The listing also moves the planned `dropColumn` for `isAdmin` to after the backfill, so `isAdmin` is still there when the backfill reads it, and its two `run` callbacks run in the order you list them, so the second one sets `member` only on the rows the first one left empty.

```ts title="migration.ts (filled in)"
#!/usr/bin/env -S node
import type { Contract as End } from '../../snapshots/<end hash>/contract';
import endContractJson from '../../snapshots/<end hash>/contract.json' with { type: 'json' };
import type { Contract as Start } from '../../snapshots/e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63/contract';
import startContractJson from '../../snapshots/e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63/contract.json' with { type: 'json' };
import type { Contract as Mid } from './intermediate';
import midContractJson from './intermediate.json' with { type: 'json' };
import { Migration, MigrationCLI, col } from '@prisma/orm-postgres/migration';
import postgresAdapter from '@prisma/orm-postgres/adapter/runtime';
import { sql } from '@prisma/orm-postgres/builder/runtime';
import { createExecutionContext, createSqlExecutionStack } from '@prisma/orm-postgres/family-runtime';
import postgresTarget, { PostgresContractSerializer } from '@prisma/orm-postgres/target/runtime';

const stack = createSqlExecutionStack({ target: postgresTarget, adapter: postgresAdapter });
const midContract = new PostgresContractSerializer().deserializeContract<Mid>(midContractJson);

const db = sql<Mid>({
  context: createExecutionContext({ contract: midContract, stack }),
  rawCodecInferer: stack.adapter.rawCodecInferer,
});

export default class M extends Migration<Start, End> {
  override readonly endContractJson = endContractJson;
  override readonly startContractJson = startContractJson;

  override get operations() {
    return [
      this.addColumn({ schema: 'public', table: 'user', column: col('role', 'text', { codecRef: { codecId: 'pg/text@1' } }) }),
      this.dataTransform(midContract, 'backfill-user-role', {
        check: () => db.public.user.select('id').where((f, fns) => fns.eq(f.role, null)).limit(1),
        run: [
          () => db.public.user.update({ role: 'admin' }).where((f, fns) => fns.eq(f.isAdmin, true)),
          () => db.public.user.update({ role: 'member' }).where((f, fns) => fns.eq(f.role, null)),
        ],
      }),
      this.setNotNull({ schema: 'public', table: 'user', column: 'role' }),
      this.dropColumn({ schema: 'public', table: 'user', column: 'isAdmin' }),
    ];
  }
}

MigrationCLI.run(import.meta.url, M);
```

If you would rather not keep an intermediate contract around, you can reach the same result by splitting the change into two migrations. First add `role` to your contract as optional and plan, and in that migration add a `dataTransform` that sets `role` from `isAdmin`, with `db` built from its end contract, which still describes both columns. Then remove `isAdmin`, make `role` required, and plan again. That second plan writes a `handle-nulls-user-role` placeholder, which you fill in with what should happen to rows where `role` is still empty. Splitting the change needs no `intermediate.*` files at all, unless `User` has a `temporal.updatedAtString()` column.

## Raw SQL [#escape-hatch-raw-sql]

When the statement you need has no method of its own, such as `COMMENT ON`, you write the SQL yourself. Import `rawSql` from `@prisma/orm-postgres/migration` and add the call to the `operations` array alongside the other operations. You also say what kind of change the statement is, by setting `operationClass` to one of the [four classes](https://www.prisma.io/docs/orm/migrations/how-migrations-work#every-operation-checks-itself): `additive`, `widening`, `destructive`, or `data`. `npx prisma db migrate` runs all four of them, and adds a data-loss warning for `destructive`. Give each operation its own `id`, because error messages name it and Prisma ORM does not check that it is unique, so two operations sharing an `id` leave you unable to tell which one an error is about. The `label` is the text the CLI prints. One limit matters before you write any SQL: one `npx prisma db migrate` run on PostgreSQL is [one transaction](https://www.prisma.io/docs/orm/migrations/applying-a-migration#when-something-goes-wrong), so `rawSql` cannot run `CREATE INDEX CONCURRENTLY`, or anything else that has to run outside a transaction. For an index, use `this.createIndex`, which is a plain `CREATE INDEX` and blocks writes while it builds. The example below is only an illustration, because it enables the `pgcrypto` PostgreSQL extension, which you would really do with `this.installExtension`:

```ts
rawSql({
  id: 'extension.pgcrypto',
  label: 'Enable extension "pgcrypto"',
  operationClass: 'additive',
  target: { id: 'postgres' },
  precheck: [
    { description: 'not yet enabled', sql: "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" },
  ],
  execute: [{ description: 'enable it', sql: 'CREATE EXTENSION IF NOT EXISTS pgcrypto' }],
  postcheck: [
    { description: 'now enabled', sql: "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" },
  ],
}),
```

Give each operation a precheck and a postcheck if you can, because they are what lets Prisma ORM decide whether your SQL needs to run at all, and whether it did what you meant. Each check is a query whose first row must have `true` in its first column. `npx prisma db migrate` runs the postcheck twice for each operation. It runs it first before the operation, where a passing postcheck means the change is already there, so the operation is skipped. When the postcheck does not pass there, the precheck runs, and if it is not true the run stops. The postcheck then runs again after the operation, and if it fails that time the run stops. That leaves two cases worth remembering: an empty `precheck` lets the operation run, and an empty `postcheck` never passes before the operation, which means that operation runs on every `npx prisma db migrate` that applies its migration.

## The same pattern on MongoDB [#the-same-pattern-on-mongodb]

The same backfill pattern works on MongoDB, with different imports and a differently shaped `check`. Import `dataTransform` from `@prisma/orm-mongo/target/migration`, and write its `check` as an object whose `source` is a callback that returns the query. The example below needs no `db` statements, because it builds its queries directly: `AggregateCommand` for the `check` query and `RawUpdateManyCommand` for the `run` update, both from `@prisma/orm-mongo/query-ast/execution`. A call such as `new RawUpdateManyCommand('products', { status: { $exists: false } }, { $set: { status: 'active' } })` takes the collection name, a filter, and an update. Here those two queries come back from the helpers `existingProductsWithoutStatus` and `backfillRun`, and each of them takes the end contract's `storageHash`, because every MongoDB query records the hash of the contract it was built for. The `setValidation` step is in the listing only because this migration also adds fields to `products`, and the collection's validator has to include them. Both helpers are in the full [retail-store example](https://github.com/prisma/orm/blob/main/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts):

```ts title="migration.ts (MongoDB, excerpt)"
import { dataTransform, setValidation } from '@prisma/orm-mongo/target/migration';

override get operations() {
  const storageHash = this.endContract.storage.storageHash;
  const productsValidator = this.endContract.collection.products.validator;
  return [
    setValidation('products', productsValidator.jsonSchema, {
      validationLevel: productsValidator.validationLevel,
      validationAction: productsValidator.validationAction,
    }),
    dataTransform('backfill-product-status', {
      check: { source: () => existingProductsWithoutStatus(storageHash) },
      run: () => backfillRun(storageHash),
    }),
  ];
}
```

## Starting from a blank migration [#starting-from-a-blank-migration]

Not every migration comes from a contract change. When you want a data-only migration, which changes rows and leaves your contract state as it is, or a migration you intend to write entirely by hand, run `npx prisma migration new`. It writes an empty `ops.json`, a `migration.json`, and a `migration.ts` that already has the snapshot imports, the class, and an empty `operations` array. Nothing was planned for you, so there is no `placeholder` import to remove, but you do have to add the `@prisma/orm-postgres` imports and the `endContract`, `stack`, and `db` statements from the filled-in `displayName` listing.

Because a data-only migration starts and ends at the contract state your database already matches, you have to tell the command which state that is, by passing `--from` with your contract's hash. `npx prisma migration graph` marks that hash `@contract`. The [`migration new` reference](https://www.prisma.io/docs/cli/migration-new) lists the rest of the options:

  

#### bun

```bash
bunx prisma migration new --name backfill_scores --from <hash>
```

#### pnpm

```bash
pnpm dlx prisma migration new --name backfill_scores --from <hash>
```

#### yarn

```bash
yarn dlx prisma migration new --name backfill_scores --from <hash>
```

#### npm

```bash
npx prisma migration new --name backfill_scores --from <hash>
```

Write your operations in the new `migration.ts` and recompile it. Until you do, the migration is still the empty one the command wrote, so `npx prisma migration check` and `npx prisma migration plan` both fail.

## Editing checklist [#editing-checklist]

1. Edit `migration.ts`, never `ops.json`, then recompile it.
2. Review the diff of `ops.json`, the file that runs.
3. Run `npx prisma migration check`, which prints `✔ All checks passed` when nothing is wrong.
4. Commit `migration.ts`, `ops.json`, `migration.json`, the `migrations/snapshots/<hash>/` directories that `migration.ts` imports, and your `contract.json` and `contract.d.ts`. If the migration has `intermediate.*` files, commit those too.

> [!NOTE]
> What's early
> 
> Hand-written MongoDB data transforms cannot use the query builder for updates. No command notices a `migration.ts` edit that you did not recompile, so recompile after every edit.

## Prompt your coding agent [#prompt-your-coding-agent]

Projects created with `npm create prisma@latest` include the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8), instruction files for your coding agent. In an existing project, run `npx prisma skills sync`. Ask your agent to:

* "Fill in the placeholder in the latest migration: backfill `displayName` with the user's email prefix."
* "Add a data transform to this migration that normalizes existing `phone` values before the unique constraint."
* "Recompile the migration I just edited and show me the ops.json diff."

## See also [#see-also]

* [Generating a migration](https://www.prisma.io/docs/orm/migrations/generating-a-migration): where the first draft of `migration.ts` comes from
* [Applying a migration](https://www.prisma.io/docs/orm/migrations/applying-a-migration): run the edited migration
* [Data Migrations in Prisma 8](https://www.prisma.io/blog/data-migrations-in-prisma-next): how `dataTransform` was designed

## Related pages

- [`Applying a migration`](https://www.prisma.io/docs/orm/migrations/applying-a-migration): The db migrate command applies the migrations you planned, until your database matches your contract, with a preview, checks on every operation, and safe re-runs.
- [`Generating a migration`](https://www.prisma.io/docs/orm/migrations/generating-a-migration): Turn a change to your contract into a migration you can review, with the migration plan command.
- [`How migrations work`](https://www.prisma.io/docs/orm/migrations/how-migrations-work): Change your contract, plan a migration, review it, apply it. Operations can check the database before and after they run.
- [`Rollbacks and recovery`](https://www.prisma.io/docs/orm/migrations/rollbacks-and-recovery): Rolling back is one more migration that makes the database match an earlier contract state. Recovery is fixing a failed migration and running it again.
- [`The migration graph`](https://www.prisma.io/docs/orm/migrations/the-migration-graph): You and a teammate each changed your Prisma contract on separate branches. The migration graph is how Prisma ORM applies both changes to every database after the branches merge.