142 lines
4.9 KiB
Markdown
142 lines
4.9 KiB
Markdown
---
|
|
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]]
|