---
title: "Database — Postgres"
aliases: [payload-postgres, payload-drizzle-postgres, postgresAdapter]
tags: [payloadcms, postgres, drizzle, database, migrations]
sources: [raw/database__postgres.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

Payload supports Postgres via `@payloadcms/db-postgres`, which wraps Drizzle ORM + `node-postgres`. A Vercel-optimised variant (`@payloadcms/db-vercel-postgres`) is also available.

## Setup

```ts
import { postgresAdapter } from '@payloadcms/db-postgres'

export default buildConfig({
  db: postgresAdapter({
    pool: {
      connectionString: process.env.DATABASE_URL,
    },
  }),
})
```

### Vercel variant

```ts
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'

export default buildConfig({
  // Auto-reads process.env.POSTGRES_URL when no options passed
  db: vercelPostgresAdapter(),
})
```

> **Gotcha:** `vercelPostgresAdapter` with a `localhost`/`127.0.0.1` connection string silently falls back to the `pg` module — `@vercel/postgres` doesn't work locally. Override with `forceUseVercelPostgres: true` only if you set up a Docker Neon DB per the Vercel guide.

## Key Options

| Option | Default | Notes |
|--------|---------|-------|
| `pool` * | — | Required. Drizzle/node-postgres pool config |
| `push` | `true` (dev only) | Auto-push schema changes in dev — disable if using migrations manually |
| `migrationDir` | — | Custom path for migration files |
| `idType` | `'serial'` | `'serial'` or `'uuid'` |
| `schemaName` | `'public'` | Postgres schema (experimental) |
| `transactionOptions` | — | `PgTransactionConfig` or `false` to disable transactions |
| `disableCreateDatabase` | `false` | Prevent auto DB creation |
| `localesSuffix` | `'_locales'` | Suffix for locale tables |
| `relationshipsSuffix` | `'_rels'` | Suffix for relationship tables |
| `versionsSuffix` | `'_v'` | Suffix for version tables |
| `readReplicas` | — | Array of replica connection strings |
| `readReplicasAfterWriteInterval` | `2000` | ms to route reads to primary after a write (prevents stale reads) |
| `blocksAsJSON` | `false` | Store blocks as JSON column — improves perf for large block counts |
| `beforeSchemaInit` | — | Hook before Drizzle schema is built |
| `afterSchemaInit` | — | Hook after Drizzle schema is built |
| `generateSchemaOutputFile` | `src/payload-generated.schema.ts` | Override generated schema output path |
| `allowIDOnCreate` | `false` | Allow passing `id` in create operations |

## Direct Drizzle Access

Generate schema first, then query via Drizzle directly:

```sh
npx payload generate:db-schema
```

```ts
import { posts } from './payload-generated-schema'
import { eq, sql, and } from '@payloadcms/db-postgres/drizzle'

// Drizzle relational query API
const allPosts = await payload.db.drizzle.query.posts.findMany()

// Drizzle select API
const result = await payload.db.drizzle
  .select()
  .from(posts)
  .where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example'`))
```

Also exposed on `payload.db`:
- `payload.db.tables` — all Drizzle table objects
- `payload.db.enums` — all Drizzle enum configs
- `payload.db.relations` — all Drizzle relations

## Development: Push vs Migrations

| Mode | Mechanism | When to use |
|------|-----------|-------------|
| Dev (`push: true`) | Drizzle auto-pushes schema changes instantly | Rapid local iteration |
| Dev (`push: false`) | Only migrations apply | Match prod workflow locally |
| Production | Always use migrations | `push` disabled in prod |

> Do **not** mix `push` and manual `migrate` commands — they conflict.

## Drizzle Schema Hooks

### `beforeSchemaInit`

Runs before Payload builds the schema. Use to inject external tables (e.g. preserving an existing DB when migrating to Payload).

```ts
postgresAdapter({
  beforeSchemaInit: [
    ({ schema, adapter }) => ({
      ...schema,
      tables: {
        ...schema.tables,
        users,     // imported from Drizzle introspection
        countries,
      },
    }),
  ],
})
```

> Use [Drizzle introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull) (`drizzle-kit introspect`) to generate schema from an existing DB.

> **Collision check:** if your existing DB has a `users` table and Payload also has a `users` collection, change the collection's `dbName` to avoid overlap.

### `afterSchemaInit`

Runs after schema is built. Use `extendTable` utility to add columns or indexes not in the Payload config.

```ts
postgresAdapter({
  afterSchemaInit: [
    ({ schema, extendTable }) => {
      extendTable({
        table: schema.tables.places,
        columns: { extraIntegerColumn: integer('extra_integer_column') },
        extraConfig: (table) => ({
          country_city_idx: index('country_city_idx').on(table.country, table.city),
        }),
      })
      return schema
    },
  ],
})
```

### Including hook-added tables in generated schema

Columns/tables added via hooks are **not** included in `payload generate:db-schema` output by default. To include them, mutate `adapter.rawTables` inside `beforeSchemaInit`:

```ts
beforeSchemaInit: [
  ({ schema, adapter }) => {
    adapter.rawTables.posts.columns.customColumn = {
      name: 'custom_column',
      type: 'integer',
      notNull: true,
    }
    adapter.rawTables.posts.indexes.customColumnIdx = {
      name: 'custom_column_idx',
      unique: true,
      on: ['custom_column'],
    }
    return schema
  },
]
```

## Key Takeaways

- Install `@payloadcms/db-postgres`; use `@payloadcms/db-vercel-postgres` only for Vercel deployments
- `push: true` (default in dev) auto-syncs schema — disable it if you want migration-only workflow
- `readReplicas` + `readReplicasAfterWriteInterval` handles read-heavy workloads without stale read risk
- `blocksAsJSON: true` can significantly improve performance when blocks are heavily used
- `beforeSchemaInit` / `afterSchemaInit` hooks allow extending the schema with non-Payload tables or columns
- Always generate Drizzle schema (`npx payload generate:db-schema`) before using typed direct Drizzle queries
- Schema hook additions require `adapter.rawTables` mutation to appear in the generated schema file

## Related

- [[wiki/payloadcms/database-overview|Database — Overview]]
- [[wiki/payloadcms/database-migrations|Database Migrations]]
- [[wiki/payloadcms/database-indexes|Database Indexes]]
- [[wiki/payloadcms/database-mongodb|Database — MongoDB]]
- [[wiki/payloadcms/configuration|Payload Config — Overview]]

## Sources

- `raw/database__postgres.md` — official Payload CMS Postgres adapter docs
