4.9 KiB
| title | aliases | tags | sources | created | updated | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Database Migrations |
|
|
|
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).
Recommended Postgres Workflow
- Local dev — use Drizzle
pushmode (default). Changes auto-sync to local DB. Do not mixpush+ migrations on the same local DB — Payload will warn. - Create migration — once feature is complete:
pnpm payload migrate:create. Generates SQL diff in/migrations. - 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
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:
import { migrations } from './migrations' // auto-generated index.ts
db: postgresAdapter({
prodMigrations: migrations,
})
Warning:
prodMigrationsslows 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
reqto Local API calls to stay inside it - Postgres users must run
payload migrateon every deploy — wire it into yourciscript - Never mix Drizzle
pushand migrations on the same local dev database migrationDircan override default./src/migrationslocation per adapterprodMigrationskey 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