obsidian/wiki/payloadcms/database-migrations.md
2026-05-15 15:13:56 +01:00

4.9 KiB

title aliases tags sources created updated
Database Migrations
payload-migrations
payload-migrate
payloadcms
database
migrations
postgres
mongodb
drizzle
typescript
raw/database__migrations.md
2026-05-15 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:

{ "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):

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).

  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:
"ci": "payload migrate && pnpm build"

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


Running Migrations in Production

Set build script to payload migrate && next build.

Runtime / Server Startup (long-running containers)

Pass migrations via prodMigrations on the adapter:

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