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