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

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]]