Migrations
Change the schema, ship the change, and check migrations in CI
A migration advances the database to a new stored record shape, object-store layout, or set of indexes. You plan migrations on your machine and commit them. Each user's browser applies the ones it hasn't run yet the next time the app opens the database.
prisma migrate and prisma db update don't apply IndexedDB migrations, because IndexedDB only exists in the browser. createAutoMigratingIdbClient applies them instead. Use @prisma-idb/driver-idb/control for the driver in prisma.config.ts. Migration files import the authoring tools from @prisma-idb/target-idb/migration.
Change the schema
After editing schema.prisma, run:
npx prisma@latest contract emit
npx prisma-idb migration plan --name add-todo-priority
npx prisma-idb migration contract-space
npx prisma-idb migration preflightcontract emitupdatescontract.jsonandcontract.d.ts.migration plancompares the new contract with the one the newest migration ends at, and writes the difference as a new package undermigrations/app/.--namesets the folder name, and is required for every migration after the first.migration contract-spaceregenerates the module that bundles the migrations into your app.migration preflightchecks that the emitted contract matches the chain head, and that the whole chain applies and produces the head contract's stores and indexes.
Commit the new package, the new migrations/snapshots/ entry and the regenerated files. The next time a user opens the app, the client applies the migration before resolving.
For a brand-new project the steps are the same without --name: the first migration is named baseline. The Quick Start walks through it.
Field-only changes also need a migration. See Author a field change for nullable additions, required-field backfills, unresolved transforms, and renames.
Destructive changes
Removing a model drops its object store, and every record in that store is deleted on each user's device when the migration runs. migration plan warns on stderr when a migration drops a store:
Warning: this migration deletes data. When it runs in a user's browser, every record in these stores is deleted:
- drafts
Check that this is intended before shipping it.The browser applies the migration as planned; there's no switch to skip or refuse it at run time. Review the change before you commit it. IndexedDB can hold data that exists nowhere else, such as drafts or offline changes that haven't synced yet. If you need to keep that data, stop writing to the model in one release and remove it in a later one.
Dropping an index loses nothing, so changing an index is always safe.
Check migrations in CI
Run preflight on every change:
npx prisma-idb migration preflightIt applies every migration, in order, to an empty in-memory IndexedDB (fake-indexeddb). It fails if the chain is broken, a package was edited without regenerating its hash, or an operation can't apply. After replay, it compares the final stores and indexes with migrations/snapshots/<head.to>/contract.json. This check always runs. It fails with a readable diff for missing or extra stores and indexes, or different key paths, autoIncrement, unique or multiEntry flags. The internal _prisma_next_marker store is excluded.
A missing or invalid head snapshot also fails preflight. So does a snapshot whose storageHash differs from head.to, or from the hash of its own content. Restore the snapshot from source control or regenerate the chain. A valid package hash alone does not prove that hand-edited operations produce the right schema.
fake-indexeddb can differ from real browsers in edge cases, so keep testing your app in a browser too.
Edit a migration by hand
migration plan writes a readable migration.ts in each package. To change what a migration does, edit that file, then run it to regenerate the package's ops.json and migration.json:
npx tsx migrations/app/<migration-folder>/migration.tsRun it again after every edit. Like migration plan, it warns if the migration drops a store. Then run migration preflight.
What's in a migration package
migrations/
app/
20260621T0854_baseline/
migration.ts the readable source; edit this
ops.json the operations the browser applies
migration.json the from and to contract hashes, and a hash of the package
snapshots/
<storageHash>/
contract.json the contract a migration ends at
contract.d.tsEach package records the contract hash it starts from and the one it ends at, so the packages form one chain from an empty database to your current schema. The next migration plan starts from the newest package's snapshot.
Extensions
An extension, such as sync, ships its own object stores and its own chain of migrations. You don't plan or edit them. When you pass the extension to createAutoMigratingIdbClient({ extensions }), its pending migrations run in the same upgrade as yours, so the database changes once, not once per chain.
Each chain's progress is recorded separately in the _prisma_next_marker store, under its own id ("app" for yours, "idb-sync" for sync). An app schema change and an extension update don't affect each other.
If you're writing an extension, pass --space <id> to migration plan to write its packages under migrations/<id>/ instead of migrations/app/.
Command reference
All three commands read prisma.config.ts, so the flags below are only needed to override it.
| Command | Flag | Default | Meaning |
|---|---|---|---|
| All | --config <path> | prisma.config.ts | The config file. |
migration plan | --name <slug> | baseline for the first migration | The package's folder name. Required after the first. |
--space <id> | app | The chain to plan. Only extension authors set this. | |
--contract <path> | The config's contract output | The emitted contract.json. | |
--migrations-dir <path> | The config's migrations.dir | The migrations folder. | |
migration contract-space | --out <path> | Next to contract.json | Where to write the generated module. |
--contract, --migrations-dir | As above | ||
migration preflight | --migrations-dir <path> | The config's migrations.dir |
The prisma CLI's read-only commands also work: prisma migration list, migration status and migration check. The commands that need a live database, such as prisma db verify, explain that IndexedDB only exists in the browser.