---
title: "Database Migrations"
aliases: [payload-migrations, payload-migrate]
tags: [payloadcms, database, migrations, postgres, mongodb, drizzle, typescript]
sources: [raw/database__migrations.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

Payload ships first-party TypeScript migrations via the `npm run payload migrate:*` CLI commands. Requires a `"payload"` script in `package.json`:

```json
{ "scripts": { "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload" } }
```

Default migrations folder: `./src/migrations`. Override via `migrationDir` on the DB adapter.

---

## Migration File Structure

Each file exports `up` (apply) and `down` (rollback):

```ts
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'

export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
  // make changes — pass `req` to keep inside the transaction
}
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
  // revert changes
}
```

---

## Transactions

Every migration runs inside a DB transaction automatically.  
Pass `req` to any Local API call or `payload.db.*` method to enroll it in the transaction.

**Direct DB access within the transaction:**

| Adapter | Pattern |
|---------|---------|
| Postgres | `import { sql } from '@payloadcms/db-postgres'` → `db.execute(sql\`...\`)` |
| MongoDB | `payload.db.collections.posts.collection.find({ session })` |
| SQLite | `db.run(sql\`...\`)` — note: transactions disabled by default |

---

## Commands

| Command | Action |
|---------|--------|
| `migrate` | Run all pending migrations |
| `migrate:create [name]` | Generate new migration file (auto-detects schema diff for Postgres) |
| `migrate:status` | Table of run / not-run migrations |
| `migrate:down` | Roll back last batch |
| `migrate:refresh` | Roll back all, then re-run all |
| `migrate:reset` | Roll back all (no re-run) |
| `migrate:fresh` | Drop everything, re-run all from scratch |

Useful flags for `migrate:create`:
- `--skip-empty` — skip "no schema changes" prompt (CI-friendly)
- `--force-accept-warning` — create blank migration even with no schema changes

---

## MongoDB vs Postgres Workflow

**MongoDB** — migrations rarely needed; only when transforming existing documents from one shape to another. Run locally against production DB using the prod connection string.

**Postgres** — migrations are **required** for any schema change (new field, new collection = errors without migrating).

### Recommended Postgres Workflow

1. **Local dev** — use Drizzle `push` mode (default). Changes auto-sync to local DB. Do **not** mix `push` + migrations on the same local DB — Payload will warn.
2. **Create migration** — once feature is complete: `pnpm payload migrate:create`. Generates SQL diff in `/migrations`.
3. **CI/build pipeline** — run migrations before build:

```json
"ci": "payload migrate && pnpm build"
```

This ensures `payload migrate` runs against production DB before every deploy. Failed migration = rejected deploy.

---

## Running Migrations in Production

### CI/Build (recommended for serverless/Vercel)
Set build script to `payload migrate && next build`.

### Runtime / Server Startup (long-running containers)
Pass migrations via `prodMigrations` on the adapter:

```ts
import { migrations } from './migrations' // auto-generated index.ts

db: postgresAdapter({
  prodMigrations: migrations,
})
```

> **Warning:** `prodMigrations` slows serverless cold starts. Use only for long-running servers/containers.

---

## Environment-Specific Migrations

Generating a migration in dev may miss production-only plugins (or vice versa), causing schema discrepancies.

**Mitigations:**
- Manually edit the generated migration file to include env-specific changes
- Temporarily set production env vars locally before running `migrate:create`
- Maintain separate migration files per environment

---

## Key Takeaways

- Every migration runs in a **transaction** — pass `req` to Local API calls to stay inside it
- Postgres users must run `payload migrate` on every deploy — wire it into your `ci` script
- **Never mix** Drizzle `push` and migrations on the same local dev database
- `migrationDir` can override default `./src/migrations` location per adapter
- `prodMigrations` key enables runtime migration on server startup (not recommended for Vercel)
- MongoDB migrations are optional; Postgres migrations are mandatory for any schema change
- Always review generated migration files before committing — they're programmatic but not infallible
- Be mindful of **environment-specific config** (e.g. plugins enabled only in prod) when generating migrations

---

## Related

- [[wiki/payloadcms/database-indexes|Database Indexes]]
- [[wiki/payloadcms/configuration|Payload Config — Overview]]
- [[wiki/payloadcms/production|Production & Performance]]
- [[wiki/payloadcms/migration-troubleshooting|Migration & Troubleshooting]]
- [[wiki/payloadcms/typescript|TypeScript]]
