_prisma_migrations table.
The _prisma_migrations Table
Every database managed by Prisma Migrate contains a migrations table that serves the same purpose as migration tables in nearly all migration tools. The terminology is SQL-specific because migrations tables are only used with SQL connectors.Prisma’s migrations table is more extensive than typical migration tools, tracking additional metadata like checksums, logs, and rollback state.
Table Schema
The table schema is identical across all SQL databases (with minor dialect differences):Column Descriptions
| Column | Type | Description |
|---|---|---|
id | VARCHAR(36) | Random unique identifier (v4 UUID). Never changes once written. |
checksum | VARCHAR(64) | SHA-256 checksum of the migration file. Used to detect modifications. Never overwritten. |
finished_at | TIMESTAMP | Completion timestamp. Only set for successful migrations. NULL means incomplete. |
migration_name | VARCHAR(255) | Complete name of the migration directory (without path prefix). |
logs | TEXT | Error messages recorded if the migration fails. |
rolled_back_at | TIMESTAMP | Set by prisma migrate resolve --rolled-back. Causes the row to be ignored. |
started_at | TIMESTAMP | Row creation timestamp, written before applying the migration. |
applied_steps_count | INTEGER | Deprecated. Should not be used. |
Migration States
A migration row can be in one of several states based on column values:Successful Migration
Failed Migration
Rolled Back
migrate resolve. Ignored during deployment.
Pending (not in table)
Migration Lifecycle
During prisma migrate deploy
migrate deploy is the command for unattended migrations (CI/CD). It exclusively:
- Reads the migrations directory on disk
- Queries
_prisma_migrationsto determine applied migrations - Checks for failed migrations (started but not finished)
- Applies pending migrations in chronological order
Deploy algorithm in detail
Deploy algorithm in detail
For each migration in the migrations directory:If row exists in migrations table:
rolled_back_at IS NOT NULL→ Skip (ignored)finished_at IS NOT NULL→ Skip (already applied)finished_at IS NULL AND rolled_back_at IS NULL→ ERROR: Failed migration must be resolved
- Insert row with
id,migration_name,checksum, andstarted_at - Execute the migration SQL
- On success: Set
finished_atto current timestamp - On error: Do nothing (started without finished = error state)
migrate deploy will never reset your database and never uses a shadow database. It’s designed for production reliability.During prisma migrate resolve
Resolves failed or stuck migrations:
With --applied <migration>:
- Marks the existing row as rolled back (
rolled_back_at = now()) - Creates a new row with
finished_at = started_at
--rolled-back <migration>:
- Sets
rolled_back_at = now()on the existing row - Future deployments will ignore this migration
During prisma migrate dev
The development workflow is more strict:
- Detects drift between migrations and actual database schema
- Checks for modified migrations (via checksum comparison)
- Checks for missing migrations (in table but not on disk)
- Offers to reset the database when inconsistencies are detected
Why does migrate dev want to reset?
Why does migrate dev want to reset?
The dev workflow is pedantic because:
- Migrations can contain arbitrary SQL (views, triggers, check constraints)
- These features might not be representable in Prisma schema
- The only way to guarantee the database matches migrations is to reset and reapply
- Common causes: branch switching, editing migrations, merge conflicts
db push to match your schema, but that would lose custom SQL features the engines don’t understand.Why No Down Migrations?
Prisma Migrate intentionally does not support automatic down/rollback migrations. Here’s why:In Development
Down migrations are typically used to:- Iterate on a migration (down → edit → up)
- Switch branches and undo changes
migrate dev detects discrepancies and offers to reset your dev database. This is simpler and more reliable than down migrations.
In Production
Down migrations seem safe but have many hidden assumptions:- ❌ The migration might have partially failed
- ❌ The migration must be reversible (no dropped tables/data)
- ❌ The down migration must work (are they tested?)
- ❌ Rolling back schema but not code causes version mismatch
- ❌ Large datasets make rollbacks too slow/lock-heavy
Recommended approach: Follow the expand-and-contract pattern and roll forward, never backward.
Manual Rollbacks
Prisma supports manual rollbacks:- Diagnose the issue
- Manually fix the database
- Use
migrate resolveto update the tracking table
Best Practices
Separate Data and Schema Migrations
Prisma recommends completely separating data migrations from schema migrations:- Use a different tool/workflow for data migrations
- Small projects can inline data changes in SQL
- Large projects should run data migrations separately
- Longer-running
- Riskier
- Harder to test
- More likely to fail
Use Transactions When Possible
Migrations are not wrapped in transactions by default for:- Determinism - Same behavior in dev and production
- Flexibility - You can add
BEGIN;/COMMIT;yourself - Consistency - Not all databases support DDL in transactions (MySQL)
- Performance - Large migrations without transactions are faster
How to add transactions
How to add transactions
Add
BEGIN; and COMMIT; to your migration file:migration.sql
Never Use IF NOT EXISTS
Generated migrations should not use IF NOT EXISTS clauses because:
- Diffing should always know if something exists
- Precision is critical for migration generation
- Conditional logic hides schema state
Drift Detection
Drift occurs when the actual database schema doesn’t match the applied migrations.In Development (migrate dev)
✅ Detects drift and offers to resolve it (usually by reset)
In Production (migrate deploy)
❌ Does not detect drift by design
Why? Drift detection requires a shadow database, which:
- Adds complexity to deployment
- Many teams are uncomfortable with temporary databases in prod pipelines
- Deploy should be light, simple, and never block deployments
deploy errs on the side of not standing in the way.
If drift occurs in production, use
prisma migrate status to diagnose, then manually resolve and use migrate resolve to update the tracking table.Related Commands
| Command | Purpose |
|---|---|
migrate deploy | Apply pending migrations (production) |
migrate dev | Create and apply migrations (development) |
migrate status | Check migration history and detect issues |
migrate resolve | Mark migrations as applied/rolled-back |
migrate reset | Reset database and reapply all migrations |
Source Code References
Key implementation files:- Table creation:
schema-engine/connectors/sql-schema-connector/src/flavour/{postgres,mysql,mssql}.rs - Migration persistence:
schema-engine/connectors/sql-schema-connector/src/sql_migration_persistence.rs - Core logic:
schema-engine/core/src/commands/