--- 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 { // make changes — pass `req` to keep inside the transaction } export async function down({ payload, req }: MigrateDownArgs): Promise { // 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]]