# Prisma Migrate, From Zero

> Schema-first migrations in the Node world: edit your Prisma schema, generate a migration, and keep dev and production in sync without drift.


---

# Prisma Migrate, From Zero

You changed your `schema.prisma`, your editor is happy, your types check, and then your app blows up at runtime because the database never got the memo. Prisma's schema is a description of what you *want*; the database is what you *have*; migrations are the bridge between them. This guide makes that bridge boring and predictable so you stop guessing whether dev and production actually match.

By the end you'll know which command to run where, what that mysterious shadow database is for, and why editing an already-applied migration is the one thing that quietly wrecks a team.

## How to read this

Read it in order the first time. Phase 1 builds the mental model (schema as source of truth, what a migration actually is) and the rest won't click without it. Phase 2 is the loop you'll run every day. Phase 3 is the stuff that bites you on a real team with a real production database, so don't skip it before you ship.

If you've never touched a migration tool before, the sibling guide [/guides/database-migrations](/guides/database-migrations) explains the idea independent of any one tool, and [/guides/how-an-orm-works](/guides/how-an-orm-works) explains what Prisma is doing underneath.

## The phases

1. [Schema is the source of truth](01-schema-is-the-source-of-truth.md) - what Prisma Migrate actually is, and what a migration file really contains.
2. [The everyday loop](02-the-everyday-loop.md) - `migrate dev`, `migrate deploy`, and the workflow you'll repeat hundreds of times.
3. [Drift, shadows, and production](03-drift-shadows-and-production.md) - the shadow database, drift detection, failed migrations, and the rules that keep a team sane.


---

# Schema is the source of truth

Here's the mental shift that makes everything else fall into place: with Prisma, you don't write SQL to change your database by hand. You edit one file that describes the shape you want, and Prisma figures out the SQL to get there. That file is `schema.prisma`, and it is the single source of truth for what your database *should* look like.

Most database tools start the other way around - you write the SQL, the database is the truth, and your code chases it. Prisma flips that. You declare the destination; Prisma writes the directions.

## What the schema looks like

A Prisma schema is a plain text file. Here's a small one:

```prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}
```

*What just happened:* you described two tables, their columns, types, a unique constraint on `email`, and a foreign-key relationship - all without writing a line of SQL. The `?` on `name` means nullable. `User` is the truth; the database doesn't exist yet.

## A migration is a frozen snapshot of one change

When you ask Prisma to apply this schema, it doesn't reach into the database live. It writes a **migration**: a folder containing a `migration.sql` file with the exact SQL needed to move the database from its current state to match the schema.

```text
prisma/
  schema.prisma
  migrations/
    20260630120000_init/
      migration.sql
    migration_lock.toml
```

*What just happened:* each migration is a timestamped folder. The folder name is a record of history. The `migration.sql` inside is the literal SQL Prisma will run. Migrations apply in timestamp order, so the folder names define the sequence of your database's life.

Open that `migration.sql` and you'll see ordinary SQL:

```sql
-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
```

*What just happened:* Prisma translated your declarative schema into concrete `CREATE TABLE` statements. This SQL is now frozen on disk. It is a fact about history - "on this date, we made these tables." That permanence is the whole point, and it's why phase 3 will tell you never to edit it after it's applied.

## Three states, and the gap between them

The reason migrations exist at all is that there are three different "shapes" in play, and they drift apart constantly:

```mermaid
flowchart LR
  S[schema.prisma<br/>what you want] --> M[migrations/<br/>recorded history]
  M --> D[(database<br/>what you have)]
  D -. compared back to .-> S
```

*What just happened:* the schema is intent, the migrations folder is the recorded path, and the database is reality. Prisma's job is to keep these three agreeing. When they disagree, that's **drift**, and detecting it is one of the things that makes Prisma trustworthy on a team - more on that in phase 3.

> Migrations are *generated*, but they are not disposable. Once a migration has run anywhere real, it's part of your project's history, the same way a Git commit is. You add new ones; you don't rewrite old ones.

## For builders

Because the schema is declarative, code review gets pleasant: a reviewer reads the `schema.prisma` diff to understand *intent* ("oh, we added a unique email constraint") and skims the generated `migration.sql` to confirm the *mechanism* is sane. Two files, two questions, no archaeology. Contrast that with a hand-written-SQL workflow where the intent only lives in the author's head.

```quiz
[
  {
    "q": "In Prisma, what is the single source of truth for the desired database shape?",
    "choices": ["The live database", "The migration.sql files", "schema.prisma", "The generated Prisma Client"],
    "answer": 2,
    "explain": "You edit schema.prisma to declare what you want; Prisma derives the SQL to get there."
  },
  {
    "q": "What does a single migration folder contain that actually changes the database?",
    "choices": ["A copy of schema.prisma", "A migration.sql file with concrete SQL", "A TypeScript script", "A JSON diff"],
    "answer": 1,
    "explain": "Each timestamped migration folder holds a migration.sql with the literal SQL Prisma runs, in timestamp order."
  },
  {
    "q": "Why are applied migration files treated as permanent history?",
    "choices": ["Prisma encrypts them", "They are large binary files", "They record what was already run, like Git commits", "The database deletes them after use"],
    "answer": 2,
    "explain": "An applied migration is a fact about what ran. You add new migrations rather than rewrite old ones."
  }
]
```


---

# The everyday loop

You'll spend almost all your Prisma time in one short loop: change the schema, make a migration, keep coding. There are really only two commands you need to internalize, and the most common mistake is using the wrong one in the wrong place. So let's nail down which is which.

## `migrate dev` - your machine, your loop

On your laptop, you run `prisma migrate dev`. It does three things in one shot, and understanding all three is what makes it feel less like magic:

```console
$ npx prisma migrate dev --name add_user_bio

Applying migration `20260630131500_add_user_bio`

The following migration(s) have been created and applied:
  migrations/
    └─ 20260630131500_add_user_bio/
        └─ migration.sql

✔ Generated Prisma Client (v5) in 84ms
```

*What just happened:* three steps, in order. (1) Prisma diffed your edited `schema.prisma` against the recorded migration history and wrote a new `migration.sql`. (2) It applied that SQL to your dev database. (3) It regenerated the Prisma Client so your TypeScript types match the new schema immediately. That third step is why your editor knows about the new column the moment the command finishes.

The `--name` is a human label, not a requirement - leave it off and Prisma will prompt you for one. Pick names that read like a changelog: `add_user_bio`, `make_email_required`, `drop_legacy_orders`.

> `migrate dev` is for development databases only. It is allowed to be destructive and will reset your dev database if history and database have diverged. Pointing it at production is the classic, painful mistake. Production gets `migrate deploy`, which we'll cover next.

## The actual daily rhythm

Here's the loop, start to finish, for adding a field:

```bash
# 1. Edit prisma/schema.prisma - add `bio String?` to model User

# 2. Create + apply the migration, regenerate the client
npx prisma migrate dev --name add_user_bio

# 3. Use the new field in code - types already updated
#    await prisma.user.update({ data: { bio: "..." } })

# 4. Commit the schema AND the new migration folder together
git add prisma/schema.prisma prisma/migrations
git commit -m "Add bio to User"
```

*What just happened:* the schema change and the generated migration travel together in one commit. This is non-negotiable - a teammate who pulls your branch runs the same migration and lands on the same database shape. If you commit the schema but forget the migration folder, their database and yours silently diverge.

## `migrate deploy` - every other environment

In CI, staging, and production, you never generate migrations. They already exist in your repo. You only *apply* the ones that haven't run yet:

```console
$ npx prisma migrate deploy

3 migrations found in prisma/migrations

Applying migration `20260630131500_add_user_bio`

All migrations have been successfully applied.
```

*What just happened:* `migrate deploy` looked at the database's record of which migrations it has already run, found the ones it hasn't, and applied them in order. It never creates a migration, never prompts, never resets anything. It is safe to run on every deploy - if there's nothing new, it does nothing.

How does the database know what it has run? Prisma keeps a bookkeeping table called `_prisma_migrations`:

```sql
SELECT migration_name, finished_at FROM "_prisma_migrations";

       migration_name          |        finished_at
-------------------------------+----------------------------
 20260630120000_init           | 2026-06-30 12:00:01.2+00
 20260630131500_add_user_bio   | 2026-06-30 13:15:02.9+00
```

*What just happened:* every successful migration writes a row here. `migrate deploy` reads this table to decide what's left to run. This is how the database remembers its own history, and it's what makes re-running deploy harmless.

## dev versus deploy, side by side

```text
                  migrate dev            migrate deploy
  Where           your laptop            CI / staging / prod
  Creates SQL?    yes                    no - only applies existing
  Applies SQL?    yes                    yes
  Regen client?   yes                    no
  Can reset DB?   yes (destructive)      no, never
  Prompts you?    yes (for a name)       no
```

*What just happened:* one command for authoring (dev), one for shipping (deploy). If you remember nothing else: you *make* migrations on your machine and *apply* them everywhere else.

## In the wild

A typical deploy pipeline runs `npx prisma migrate deploy` as a release step *before* the new app code starts serving traffic. That ordering matters: the schema must be in place before code that depends on it runs. Many teams make it a dedicated step in their deploy script so a migration failure stops the release instead of half-deploying. The companion guide [/guides/database-migrations](/guides/database-migrations) digs into the general patterns for sequencing migrations with deploys.

```quiz
[
  {
    "q": "Which command do you run on production to apply migrations?",
    "choices": ["prisma migrate dev", "prisma migrate deploy", "prisma db push", "prisma generate"],
    "answer": 1,
    "explain": "migrate deploy only applies existing migrations, never creates or resets - safe for prod. migrate dev is for your laptop."
  },
  {
    "q": "What are the THREE things `prisma migrate dev` does in one run?",
    "choices": ["Lint, test, deploy", "Create the migration, apply it, regenerate the client", "Backup, migrate, restart", "Pull, diff, push"],
    "answer": 1,
    "explain": "It diffs the schema to create a new migration.sql, applies it to the dev DB, and regenerates the Prisma Client."
  },
  {
    "q": "How does the database know which migrations it has already applied?",
    "choices": ["By comparing file timestamps", "From a _prisma_migrations bookkeeping table", "It re-runs all of them every time", "From schema.prisma comments"],
    "answer": 1,
    "explain": "Each applied migration writes a row to _prisma_migrations; migrate deploy reads it to find what's left to run."
  }
]
```


---

# Drift, shadows, and production

The everyday loop is smooth until someone reaches into the database by hand, or a migration half-fails in production, or two people generate migrations on the same day. This phase is about those moments - the ones that turn a calm Tuesday into an incident. None of them are mysterious once you understand what Prisma is checking and why.

## The shadow database, demystified

When `migrate dev` generates a migration, it needs to answer a sharp question: *does the recorded migration history actually produce the schema I wrote?* To check this without touching your real dev data, Prisma spins up a temporary, throwaway database - the **shadow database** - replays every migration into it from scratch, and compares the result against your schema.

```console
$ npx prisma migrate dev --name add_index

Prisma needs to create a shadow database...
Replaying migrations into shadow database...
Comparing against schema.prisma...

The following migration(s) have been created and applied:
  └─ 20260630140000_add_index/
```

*What just happened:* Prisma built a clean database, ran your whole migration history into it, and used that as a trustworthy reference point to compute the diff for the new migration. It's created and dropped automatically. The shadow database is *only* used by `migrate dev` - `migrate deploy` never needs one, which is good, because production database users often can't create databases.

> If your dev database user lacks permission to create a database, the shadow step fails. The fix is to point Prisma at a separate shadow database you provision yourself, via the `shadowDatabaseUrl` field in your `datasource` block. This is common on hosted Postgres where your user can't create databases.

## Drift detection: when reality diverges

**Drift** is when the actual database no longer matches what the migration history says it should be. Someone added a column in a SQL console, or restored an old backup, or edited a migration after it ran. `migrate dev` catches this and refuses to proceed quietly:

```console
$ npx prisma migrate dev

Drift detected: Your database schema is not in sync with your migration history.

[+] Added column `phone` to table `User`
```

*What just happened:* Prisma replayed history into the shadow database, compared it to your real database, and found a column in reality that no migration ever created. It stops rather than generating a migration on top of an unknown state. In development, the resolution is usually to let Prisma reset and reapply (you'll lose dev data), or to fold that manual change into a proper migration so history tells the truth again.

## The one rule: never edit an applied migration

Here is the rule that protects the whole system: **once a migration has been applied anywhere real, never change its `migration.sql`.**

Why? Because Prisma records a checksum of each migration in `_prisma_migrations`. On the next `migrate deploy`, it verifies that the file on disk still matches what was applied. Edit the file and the checksums disagree:

```console
$ npx prisma migrate deploy

Error: The migration `20260630131500_add_user_bio` was modified after it was applied.
```

*What just happened:* the on-disk file no longer matches the recorded checksum, so deploy halts. This is a feature, not an annoyance - it guarantees that what ran on production is exactly what's in your repo. If you need to change something, you add a *new* migration that alters it. History is append-only, like a ledger.

## When a migration fails halfway

In production, a migration can fail partway - a unique constraint hits existing duplicate data, a statement times out. Prisma marks that migration as failed in `_prisma_migrations`, and subsequent `migrate deploy` runs refuse to continue until you resolve it:

```console
$ npx prisma migrate resolve --rolled-back 20260630131500_add_user_bio
```

*What just happened:* you told Prisma "I manually reverted that failed migration's effects; mark it rolled back so it can be retried." Use `--applied` instead if you finished the migration's work by hand and want Prisma to consider it done. Either way, *you* fix the database state; `resolve` only updates Prisma's bookkeeping to match the reality you've restored.

> Before risky migrations on a live database, take a backup. Prisma does not roll back automatically across statements on every database, so a partial failure can leave you in a state only a human (or a backup) can sort out. Test the migration against a copy of production data first when the change touches a large or constrained table.

## A team-safe checklist

```text
- Commit schema.prisma AND the new migration folder in the same commit.
- Never run `migrate dev` against staging or production.
- Never edit a migration that has already been applied anywhere; add a new one.
- Run `migrate deploy` as a release step before new code serves traffic.
- Back up production before migrations that touch large or constrained tables.
- Rebased a branch and now have two migrations same-day? Verify their order
  by timestamp and that both still apply cleanly into a fresh database.
```

*What just happened:* every item traces back to the same idea from phase 1 - schema, history, and database must keep agreeing. Drift detection and checksums are the guardrails; this checklist is how you avoid tripping them.

## In the wild

On teams, the painful drift case is two developers each running `migrate dev` the same day on separate branches. Both migrations apply fine alone, but when both land on `main`, their timestamp order may differ from the order they were authored in. Before merging, it's worth confirming the combined set applies cleanly into a fresh database - that's exactly what the shadow database does for you locally, and what a CI step running `migrate deploy` against an empty database confirms for the team. For the deeper theory of ordering and reversibility, see [/guides/database-migrations](/guides/database-migrations).

```quiz
[
  {
    "q": "What is the shadow database used for?",
    "choices": ["Storing production backups", "Replaying migration history to safely compute a new migration's diff", "Caching query results", "Holding rolled-back migrations"],
    "answer": 1,
    "explain": "migrate dev builds a clean throwaway DB, replays all migrations, and compares to schema.prisma to generate the next migration safely."
  },
  {
    "q": "Why must you never edit a migration that has already been applied?",
    "choices": ["It makes the file too large", "Prisma verifies a checksum and deploy halts if the file changed", "The database deletes edited files", "Editing is fine if you're careful"],
    "answer": 1,
    "explain": "Each applied migration's checksum is recorded; editing the file breaks the match so deploy stops, guaranteeing repo == what ran."
  },
  {
    "q": "A production migration failed halfway. What does `prisma migrate resolve` actually do?",
    "choices": ["Automatically rolls back the SQL", "Updates Prisma's bookkeeping to match the state you fixed by hand", "Deletes the failed migration file", "Restores the last backup"],
    "answer": 1,
    "explain": "resolve only updates _prisma_migrations (marking applied or rolled-back). You restore the actual database state yourself."
  }
]
```
