The officially supported PostgreSQL database adapter for Payload CMS with Drizzle ORM integration
npx @tessl/cli install tessl/npm-payloadcms--db-postgres@3.54.0The officially supported PostgreSQL database adapter for Payload CMS. This package provides a complete database abstraction layer using Drizzle ORM to handle PostgreSQL database operations including CRUD operations, migrations, schema management, transactions, and versioning.
npm install @payloadcms/db-postgresimport { postgresAdapter, sql, geometryColumn } from "@payloadcms/db-postgres";
import type { PostgresAdapterArgs, PostgresAdapter, GeneratedDatabaseSchema, MigrateUpArgs, MigrateDownArgs } from "@payloadcms/db-postgres";For migration utilities:
import { migratePostgresV2toV3 } from "@payloadcms/db-postgres/migration-utils";For Drizzle ORM integration:
import { eq, and, or } from "@payloadcms/db-postgres/drizzle";
import { pgTable, text, integer } from "@payloadcms/db-postgres/drizzle/pg-core";
import { drizzle } from "drizzle-orm/node-postgres";import { buildConfig } from 'payload';
import { postgresAdapter } from '@payloadcms/db-postgres';
export default buildConfig({
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI,
},
// Optional configuration
idType: 'serial', // or 'uuid'
schemaName: 'my_schema', // experimental
extensions: ['uuid-ossp', 'postgis'],
}),
// ...rest of config
});The @payloadcms/db-postgres adapter is built around several key components:
postgresAdapter() function creates a configured database adapter instanceMain factory function and configuration for creating PostgreSQL database adapters with comprehensive features including connection pooling, schema management, and transaction support.
function postgresAdapter(args: PostgresAdapterArgs): DatabaseAdapterObj<PostgresAdapter>;
interface PostgresAdapterArgs {
/** PostgreSQL connection pool configuration (required) */
pool: PoolConfig;
/** Primary key type for generated IDs */
idType?: 'serial' | 'uuid';
/** Database schema name (experimental) */
schemaName?: string;
/** PostgreSQL extensions to enable */
extensions?: string[];
/** Directory containing migration files */
migrationDir?: string;
/** Allow custom ID values in create operations */
allowIDOnCreate?: boolean;
/** Store blocks as JSON instead of relational structure */
blocksAsJSON?: boolean;
/** Disable automatic database creation */
disableCreateDatabase?: boolean;
/** Suffix for locale tables */
localesSuffix?: string;
/** Suffix for relationship tables */
relationshipsSuffix?: string;
/** Suffix for version tables */
versionsSuffix?: string;
/** Filter specific tables */
tablesFilter?: string[];
/** Read replica connection strings */
readReplicas?: string[];
/** Transaction configuration or false to disable */
transactionOptions?: false | PgTransactionConfig;
/** Schema transformation hooks before initialization */
beforeSchemaInit?: PostgresSchemaHook[];
/** Schema transformation hooks after initialization */
afterSchemaInit?: PostgresSchemaHook[];
/** Drizzle query logger configuration */
logger?: DrizzleConfig['logger'];
/** Custom pg dependency injection */
pg?: typeof import('pg');
/** Enable schema pushing in development */
push?: boolean;
/** Production migration definitions */
prodMigrations?: Array<{
name: string;
up: (args: MigrateUpArgs) => Promise<void>;
down: (args: MigrateDownArgs) => Promise<void>;
}>;
}Raw SQL query builder for complex database operations that need to bypass the ORM.
const sql: SQL;Usage Example:
import { sql } from "@payloadcms/db-postgres";
// Raw SQL queries in Payload hooks
const result = await payload.db.drizzle.execute(sql`
SELECT * FROM users
WHERE created_at > ${new Date('2023-01-01')}
`);Helper function for creating PostGIS geometry columns for spatial data.
function geometryColumn(
name: string,
type: 'POINT' | 'LINESTRING' | 'POLYGON' | 'MULTIPOINT' | 'MULTILINESTRING' | 'MULTIPOLYGON',
srid?: number
): PgGeometry;Usage Example:
import { geometryColumn } from "@payloadcms/db-postgres";
import { pgTable, text, uuid } from "@payloadcms/db-postgres/drizzle/pg-core";
const locations = pgTable('locations', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
point: geometryColumn('point', 'POINT', 4326),
});Migration helper functions for upgrading PostgreSQL schemas from older versions of Payload.
function migratePostgresV2toV3(args: MigrateUpArgs): Promise<void>;Complete Drizzle ORM functionality re-exported for direct database operations, schema definitions, and raw SQL queries.
// Core Drizzle exports
const sql: SQL;
function eq<T>(left: T, right: T): SQLWrapper;
function and(...conditions: SQLWrapper[]): SQLWrapper;
function or(...conditions: SQLWrapper[]): SQLWrapper;
// PostgreSQL-specific exports
function pgTable(name: string, columns: Record<string, any>): PgTable;
function text(name?: string): PgText;
function integer(name?: string): PgInteger;
function pgEnum<T extends string>(name: string, values: readonly [T, ...T[]]): PgEnum<T>;
function pgSchema(name: string): PgSchema;
// Node.js PostgreSQL driver integration
function drizzle(options: DrizzleConfig): NodePgDatabase;interface PostgresAdapter extends BasePostgresAdapter {
drizzle: Drizzle;
pg: typeof import('pg');
pool: Pool;
poolOptions: PoolConfig;
}
interface GeneratedDatabaseSchema {
schemaUntyped: Record<string, unknown>;
}
type Drizzle =
| NodePgDatabase<ResolveSchemaType<GeneratedDatabaseSchema>>
| PgWithReplicas<PgDatabase<PgQueryResultHKT, Record<string, unknown>, ExtractTablesWithRelations<Record<string, unknown>>>>;
interface DatabaseAdapterObj<T> {
name: string;
allowIDOnCreate: boolean;
defaultIDType: 'number' | 'text';
init: (options: { payload: Payload }) => T;
}
interface PoolConfig {
connectionString?: string;
host?: string;
port?: number;
user?: string;
password?: string;
database?: string;
ssl?: boolean | object;
max?: number;
min?: number;
idle?: number;
acquire?: number;
evict?: number;
}
interface PgTransactionConfig {
isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable';
accessMode?: 'read only' | 'read write';
deferrable?: boolean;
}
type PostgresSchemaHook = (args: { schema: Record<string, unknown> }) => Record<string, unknown>;
interface MigrateUpArgs {
/** The Postgres Drizzle instance for executing SQL directly within the current transaction */
db: PostgresDB;
/** The Payload instance for executing Local API methods */
payload: Payload;
/** The PayloadRequest object containing the current transaction */
req: PayloadRequest;
}
interface MigrateDownArgs {
/** The Postgres Drizzle instance for executing SQL directly within the current transaction */
db: PostgresDB;
/** The Payload instance for executing Local API methods */
payload: Payload;
/** The PayloadRequest object containing the current transaction */
req: PayloadRequest;
}
// External types from dependencies (for reference)
interface Payload {
/** Payload CMS instance - see Payload CMS documentation */
}
interface PayloadRequest {
/** Payload request object containing transaction context - see Payload CMS documentation */
}
interface PostgresDB {
/** PostgreSQL database instance from Drizzle ORM - see Drizzle ORM documentation */
}
interface BasePostgresAdapter {
/** Base PostgreSQL adapter interface from @payloadcms/drizzle package */
}
type Pool = import('pg').Pool;
type NodePgDatabase<T> = import('drizzle-orm/node-postgres').NodePgDatabase<T>;
type PgWithReplicas<T> = import('drizzle-orm/pg-core').PgWithReplicas<T>;
type PgDatabase<A, B, C> = import('drizzle-orm/pg-core').PgDatabase<A, B, C>;
type PgQueryResultHKT = import('drizzle-orm/pg-core').PgQueryResultHKT;
type ExtractTablesWithRelations<T> = import('drizzle-orm').ExtractTablesWithRelations<T>;
type ResolveSchemaType<T> = 'schema' extends keyof T ? T['schema'] : GeneratedDatabaseSchema['schemaUntyped'];