Prisma Next is in early access.Read the docs

Generating a migration

Turn a contract change into a reviewable migration with prisma-next migration plan.

This tutorial takes a contract change from your editor to a planned migration on disk. Planning is fully offline: migration plan reads your emitted contract and your existing migrations; it never connects to a database, so you can plan on a plane, in CI, or in a sandbox with no credentials.

Start from a minimal project; npx create-prisma@next scaffolds one.

Your first migration

Say your contract has a single model:

contract.prisma
model User {
  id    Int     @id
  email String
  name  String?

  @@map("user")
}

First, emit the contract. This compiles the schema into the contract.json artifact that every other command reads:

npx prisma-next contract emit

Now plan. --name sets the human-readable part of the directory name; without it the directory is called migration:

npx prisma-next migration plan --name init
✔ Planned 2 operation(s)


├─ Create schema "public"
└─ Create table "user"

from:   null
to:     sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5
App space → migrations/app/20260707T1005_init

Next: review migrations/app/20260707T1005_init if needed, then run prisma-next migrate.

DDL preview

CREATE SCHEMA IF NOT EXISTS "public";
CREATE TABLE "public"."user" (
  "email" text NOT NULL,
  "id" int4 NOT NULL,
  "name" text,
  PRIMARY KEY ("id")
);

Four things to notice:

  • The DDL preview is right there. You see the exact SQL before anything exists but files.
  • from: null means this migration starts from an empty database; it's the root of your migration graph.
  • to: is your contract's hash. The migration promises to deliver a database matching exactly the contract you just emitted.
  • App space is your application's migration lane. Database extensions bring their own lanes; see extension spaces.

The planned migration directory contains the TypeScript source, the compiled operations, and the contract snapshots:

migrations/app/20260707T1005_init/
├── migration.ts
├── ops.json
├── migration.json
└── end-contract.json  (+ end-contract.d.ts)

Open migration.ts. It reads like a description of the change, because it is one:

migrations/app/20260707T1005_init/migration.ts
import type { Contract as End } from './end-contract';
import endContract from './end-contract.json' with { type: 'json' };
import { Migration, MigrationCLI, col, primaryKey } from '@prisma-next/postgres/migration';

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

  override get operations() {
    return [
      this.createSchema({ schema: 'public' }),
      this.createTable({
        schema: 'public',
        table: 'user',
        columns: [
          col('email', 'text', { notNull: true, codecRef: { codecId: 'pg/text@1' } }),
          col('id', 'int4', { notNull: true, codecRef: { codecId: 'pg/int4@1' } }),
          col('name', 'text', { codecRef: { codecId: 'pg/text@1' } }),
        ],
        constraints: [primaryKey(['id'])],
      }),
    ];
  }
}

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

Two notes on reading it: the col(...) calls mirror your contract fields, and you never write codecRef by hand. The planner fills it in to pin how values convert between TypeScript and Postgres; treat it as noise when reviewing.

For a simple change you don't need to touch this file. When you do (to add a data backfill, reorder operations, or drop in raw SQL), that's Editing a migration.

The second migration: planning a delta

Apply the first migration (covered properly in Applying a migration), then make another change: add a phone field:

model User {
  id    Int     @id
  email String
  name  String?
  phone String?

  @@map("user")
}

Emit and plan again. This time, tell the planner where to start from, so it plans the delta rather than the whole schema:

npx prisma-next contract emit
npx prisma-next migration plan --name add_user_phone --from 20260707T1005_init
✔ Planned 1 operation(s)


└─ Add column "phone" to "user"

from:   sha256:705b1a62f26f0913caa4bfe3f8b7cb491a1b94bd47fc43471d8711bc480bcbb5
to:     sha256:925198f3cc272c5fd19c24ac02f251661775ddac21cdac4e634bbc0dda8b2d72
App space → migrations/app/20260707T1006_add_user_phone

DDL preview

ALTER TABLE "public"."user" ADD COLUMN "phone" text;

--from accepts a migration directory name (as here), a contract hash or unambiguous prefix, a ref name, or <dir>^ meaning "the state before that migration".

The db ref: skipping --from

Passing --from every time gets old. The planner's default is smarter: if a ref named db exists, planning starts from whatever it points at. Keep it advanced as part of applying:

npx prisma-next migrate --advance-ref db
npx prisma-next migration plan --name next_change   # starts from the db ref automatically

When the planner needs your input

Some changes can't be planned from the schema diff alone. Add a required field to a table that already has rows, and the planner can write the ADD COLUMN and the SET NOT NULL, but only you know what the existing rows should contain. Rather than guessing, it scaffolds the decision as a placeholder:

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

Open migration.ts and replace each `placeholder(...)` call with your actual query.
Then run: node migrations/app/20260707T1008_add_display_name/migration.ts

The generated migration sandwiches a dataTransform between the schema steps, with placeholder(...) where your backfill query goes. Filling it in is the subject of Editing a migration.

Reviewing what you planned

Three offline commands close the loop before anything runs:

# One migration in detail: operations, metadata, DDL preview
npx prisma-next migration show 20260707T1006_add_user_phone

# The whole graph, with your new edge in place
npx prisma-next migration graph

# Integrity check: hashes match, files complete, graph well-formed; exits non-zero on failure
npx prisma-next migration check

migration check is designed for CI: exit code 0 means clean, 2 means it couldn't resolve what you asked for, 4 means an integrity failure, for example someone hand-edited ops.json without re-running migration.ts.

Prompt your coding agent

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

  • "Add a required displayName field to User, emit the contract, and plan the migration."
  • "Plan a migration named add-orders-table and show me its DDL preview before I commit it."
  • "Run migration check and explain any integrity failures."

See also

On this page