obsidian/wiki/concepts/payload-cms-push-dev-prod.md
2026-05-10 22:37:43 +01:00

3.1 KiB

name description type
payload-cms-push-dev-prod Payload CMS postgresAdapter push:false vs push:true — conditional schema auto-push for dev, migration-only for prod concept

Payload CMS — push Config for postgresAdapter (Dev vs Prod)

What push Does

In payload.config.ts, postgresAdapter has a push option:

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

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

# 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
CMD ["sh", "-c", "npx payload migrate && node server.js"]

Or in docker-compose.yml:

services:
  web:
    command: sh -c "npx payload migrate && node .next/standalone/server.js"
    depends_on:
      db:
        condition: service_healthy

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