Skip to main content
PostgreSQL is a powerful open-source relational database. Better Auth uses Kysely under the hood for PostgreSQL, which means any database Kysely supports is also supported.

Installation

1

Install the pg driver

npm install pg
npm install --save-dev @types/pg
2

Configure Better Auth

Pass a pg.Pool instance directly to the database option:
auth.ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";

export const auth = betterAuth({
  database: new Pool({
    connectionString: process.env.DATABASE_URL,
  }),
});

Schema generation and migration

The Better Auth CLI can both generate the schema and apply migrations directly against your PostgreSQL database.
npx auth@latest migrate
CommandSupported
npx auth@latest generateYes
npx auth@latest migrateYes

Connection pooling

Use pg.Pool (not pg.Client) so that Better Auth can reuse connections across requests. Configure pool size based on your workload:
auth.ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,           // maximum number of clients in the pool
  idleTimeoutMillis: 30_000,  // close idle clients after 30 seconds
  connectionTimeoutMillis: 2_000, // return an error after 2 seconds if no connection is available
});

export const auth = betterAuth({
  database: pool,
});
For serverless deployments, consider using a connection pooler such as PgBouncer or Neon’s pooled connection string to avoid exhausting database connections.

Using a non-default schema

By default Better Auth creates tables in the public schema. To use a different schema (e.g. auth), you have three options.
auth.ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";

export const auth = betterAuth({
  database: new Pool({
    connectionString:
      "postgres://user:password@localhost:5432/mydb?options=-c search_path=auth",
  }),
});
URL-encode the parameter if needed: ?options=-c%20search_path%3Dauth.

Option 2: Set search_path in Pool options

auth.ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";

export const auth = betterAuth({
  database: new Pool({
    host: "localhost",
    port: 5432,
    user: "postgres",
    password: "password",
    database: "mydb",
    options: "-c search_path=auth",
  }),
});

Option 3: Set the default schema for the database user

ALTER USER your_user SET search_path TO auth;
Reconnect after running this command for the change to take effect.

Prerequisites for a non-default schema

Before using a custom schema, make sure it exists and your user has the necessary permissions:
CREATE SCHEMA IF NOT EXISTS auth;

GRANT ALL PRIVILEGES ON SCHEMA auth TO your_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO your_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT ALL ON TABLES TO your_user;
When running npx auth migrate, the CLI automatically detects your configured search_path and creates tables in the correct schema. Tables in other schemas are ignored.

Experimental joins

Enabling joins allows Better Auth to use SQL JOIN clauses to fetch related data in a single query instead of multiple round-trips. Endpoints such as /get-session and /get-full-organization see 2-3x latency improvements with high-latency databases.
auth.ts
export const auth = betterAuth({
  experimental: { joins: true },
});
You may need to run migrations after enabling joins.

Build docs developers (and LLMs) love