--- name: payload-cms-push-dev-prod description: Payload CMS postgresAdapter push:false vs push:true — conditional schema auto-push for dev, migration-only for prod type: concept --- # Payload CMS — `push` Config for `postgresAdapter` (Dev vs Prod) ## What `push` Does In `payload.config.ts`, `postgresAdapter` has a `push` option: ```ts import { postgresAdapter } from '@payloadcms/db-postgres' export default buildConfig({ db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI }, push: true, // or false }), }) ``` - **`push: true`** — Payload calls `drizzle-kit push` at startup, auto-syncing the schema to the DB. No migrations needed. Schema changes are applied immediately on every restart. - **`push: false`** (default) — Payload uses `drizzle-kit migrate` style. Schema is only updated by running explicit migration files in `migrationDir`. ## The Critical Gotcha **`push` defaults to `false`.** After wiping the database volume (e.g., during development or staging reset), `push: false` means **tables are never created**. The admin panel returns SQL errors: ``` relation "users" does not exist relation "payload_preferences" does not exist ``` This is a silent failure — there are no startup errors, just 500s when the admin panel tries to query tables that don't exist. ## The Correct Pattern ```ts postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI }, // Auto-push in dev (no migration workflow needed) // Migration-only in prod (controlled, auditable, reversible) push: process.env.NODE_ENV === 'development', migrationDir: './migrations', }) ``` ### Why Not Always `push: true`? In production `push: true` means every deployment auto-alters the database schema. This is dangerous because: - Column renames/drops are immediate and irreversible on the running DB - No migration history → can't rollback schema changes - Concurrent deployments can race on schema changes ### Why Not Always `push: false`? In development `push: false` requires running `npx payload migrate` after every collection change. This adds friction and is easy to forget, leading to confusing "table not found" errors after volume resets. ## Running Migrations in Production ```bash # Generate a migration from current schema changes npx payload migrate:create --name add-status-to-leads # Apply all pending migrations npx payload migrate # Check migration status npx payload migrate:status ``` ## Docker Compose Entrypoint Pattern Run migrations before starting the server in production: ```dockerfile # Dockerfile CMD ["sh", "-c", "npx payload migrate && node server.js"] ``` Or in `docker-compose.yml`: ```yaml services: web: command: sh -c "npx payload migrate && node .next/standalone/server.js" depends_on: db: condition: service_healthy ``` ## Related Scaffold Issue When generating a new Payload + Next.js project, the scaffold (as of Payload 3.x) may omit `push: false` in the production config template. Always check `payload.config.ts` before deploying to production. **Source:** daily/2026-05-09.md | 2026-05-09