Prisma IDB FaviconPrisma IDB

Migrations

Plan, commit, validate, and ship IndexedDB schema changes

Prisma Next IDB migrations are source-controlled packages. The CLI creates and validates them; the browser applies them when the client opens.

IndexedDB migrations are not applied by prisma-next migrate or db update. IndexedDB exists in the browser, so the runtime apply path is createAutoMigratingIdbClient().

First migration

Run this once in a new project:

npx prisma-next contract emit
npx prisma-next-idb migration plan
npx prisma-next-idb migration contract-space
npx prisma-next-idb migration preflight

migration plan auto-detects that no migration history exists yet and creates migrations/app/<timestamp>_baseline/ from an empty database.

--contract / --migrations-dir default to whatever your prisma-next.config.ts declares (contract.output / migrations.dir) — the same config prisma-next contract emit reads. Pass --config <path> if it isn't at the default location, or --contract/--migrations-dir directly to override either value without touching the config at all. If your generated contract-space file should live somewhere other than colocated with contract.json, add --out <path> to migration contract-space.

Later schema changes

For normal schema changes:

npx prisma-next contract emit
npx prisma-next-idb migration plan --name add-todo-priority
npx prisma-next-idb migration contract-space
npx prisma-next-idb migration preflight

Once a baseline exists, migration plan auto-detects the existing chain instead: it reads the head migration's end-contract.json as the from-state, diffs it against the newly emitted contract, and writes the next package under migrations/app/ with the correct from hash linking it to the previous migration. --name is required in this case — there's no default slug once it's no longer a fresh baseline.

Package layout

Each migration package contains:

migrations/app/20260621T0854_baseline/
  migration.ts
  migration.json
  ops.json
  end-contract.json
  end-contract.d.ts

migration.ts is the readable source. ops.json and migration.json are the artifacts the runtime consumes. end-contract.json is used as the starting point for the next plan.

Commit the whole package.

Inspect and validate

Preflight walks the full migration chain from an empty database to the current head against fake-indexeddb:

npx prisma-next-idb migration preflight

Run this in CI to catch broken package ordering, tampered migration hashes, and DDL operations that cannot apply cleanly.

Manual migrations

migration plan also gives you a scaffold you can then hand-edit:

npx prisma-next-idb migration plan --name custom-idb-change
# edit migrations/app/<migration-dir>/migration.ts as needed
npx tsx migrations/app/<migration-dir>/migration.ts

Running the file self-emits ops.json and migration.json from whatever describe() and operations you have. Re-run it after each edit. Use tsx or your project's TypeScript runner for this manual path.

Destructive changes

Dropping an object store or index is marked as destructive. By default, createAutoMigratingIdbClient() refuses to apply them so a deploy cannot silently wipe local user data. Review the migration carefully before opting in — see Client.

Extension spaces

Everything above describes the app space — your own schema's migration history under migrations/app/. An IDB extension (like the sync extension) contributes its own extension space: its own object stores, its own migration chain, versioned independently of your app space.

You don't generate or edit an extension's migrations — the package ships them, and createAutoMigratingIdbClient({ extensions: [...] }) applies them. At runtime, every pending space (your app space, plus each extension space) is combined into one upgradeneeded transaction and one marker-write transaction, so a cold start needing both still triggers exactly one version bump — not one per space. Each space's progress is tracked separately in the database's _prisma_next_marker store, keyed by space id ("app", "idb-sync", …), so an app schema change and a sync-extension update apply independently of each other.

This only matters if you're authoring an extension package yourself: migration plan takes a --space <id> flag that writes the migration package directly under migrations/<id>/ in the extension's own repo, instead of migrations/app/. App consumers never pass --space.

On this page