Skip to main content
Drizzle ORM is a type-safe SQL query builder and ORM for TypeScript. Better Auth ships a first-class Drizzle adapter that supports PostgreSQL, MySQL, and SQLite.

Installation

1

Install Drizzle ORM and the Better Auth adapter

npm install drizzle-orm drizzle-kit
2

Configure the adapter

Import drizzleAdapter from better-auth/adapters/drizzle and pass your Drizzle db instance:
auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./database";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg", // "pg" | "mysql" | "sqlite"
  }),
});

Configuration options

The drizzleAdapter function accepts a Drizzle db instance and a config object:
OptionTypeDescription
provider"pg" | "mysql" | "sqlite"The database provider. Required.
schemaRecord<string, any>Override the auto-detected schema. Useful when table names differ from model names.
usePluralbooleanSet to true when all your table exports use plural names (e.g. users instead of user).
camelCasebooleanUse camelCase for generated field names instead of snake_case. Defaults to false.
debugLogsbooleanEnable verbose adapter logging. Defaults to false.
transactionbooleanWrap multi-step operations in a database transaction. Defaults to false.

Schema generation

The Better Auth CLI generates the required Drizzle schema based on your auth configuration and any plugins you have enabled.
npx auth@latest generate
After generating the schema file, apply it to your database with Drizzle Kit:
npx drizzle-kit generate

Schema examples

The tables below are what npx auth@latest generate produces for each provider.
schema.ts
import { relations } from "drizzle-orm";
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  emailVerified: boolean("email_verified").default(false).notNull(),
  image: text("image"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at")
    .defaultNow()
    .$onUpdate(() => new Date())
    .notNull(),
});

export const session = pgTable(
  "session",
  {
    id: text("id").primaryKey(),
    expiresAt: timestamp("expires_at").notNull(),
    token: text("token").notNull().unique(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at")
      .$onUpdate(() => new Date())
      .notNull(),
    ipAddress: text("ip_address"),
    userAgent: text("user_agent"),
    userId: text("user_id")
      .notNull()
      .references(() => user.id, { onDelete: "cascade" }),
  },
  (table) => [index("session_userId_idx").on(table.userId)],
);

export const account = pgTable(
  "account",
  {
    id: text("id").primaryKey(),
    accountId: text("account_id").notNull(),
    providerId: text("provider_id").notNull(),
    userId: text("user_id")
      .notNull()
      .references(() => user.id, { onDelete: "cascade" }),
    accessToken: text("access_token"),
    refreshToken: text("refresh_token"),
    idToken: text("id_token"),
    accessTokenExpiresAt: timestamp("access_token_expires_at"),
    refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
    scope: text("scope"),
    password: text("password"),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at")
      .$onUpdate(() => new Date())
      .notNull(),
  },
  (table) => [index("account_userId_idx").on(table.userId)],
);

export const verification = pgTable(
  "verification",
  {
    id: text("id").primaryKey(),
    identifier: text("identifier").notNull(),
    value: text("value").notNull(),
    expiresAt: timestamp("expires_at").notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at")
      .defaultNow()
      .$onUpdate(() => new Date())
      .notNull(),
  },
  (table) => [index("verification_identifier_idx").on(table.identifier)],
);

export const userRelations = relations(user, ({ many }) => ({
  sessions: many(session),
  accounts: many(account),
}));

export const sessionRelations = relations(session, ({ one }) => ({
  user: one(user, { fields: [session.userId], references: [user.id] }),
}));

export const accountRelations = relations(account, ({ one }) => ({
  user: one(user, { fields: [account.userId], references: [user.id] }),
}));

Modifying table names

If your Drizzle schema uses different table export names (for example users instead of user), pass the schema object and remap the key:
auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./drizzle";
import * as schema from "./schema";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: {
      ...schema,
      user: schema.users, // remap plural export to the singular model name
    },
  }),
});
Alternatively, configure modelName directly in the auth config:
auth.ts
export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg", schema }),
  user: {
    modelName: "users",
  },
});

Modifying field names

Better Auth maps field names using the property name you assign in your Drizzle schema. To rename a column in the database without changing the property name, update the column string argument:
export const user = mysqlTable("user", {
  // The JS property stays `email`; the DB column becomes `email_address`
  email: varchar("email_address", { length: 255 }).notNull().unique(),
});
You can also map field names through the auth config:
auth.ts
export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "mysql", schema }),
  user: {
    fields: {
      email: "email_address",
    },
  },
});

Using plural table names

If every table in your schema uses a plural name, pass usePlural: true instead of remapping each model:
auth.ts
export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
    usePlural: true,
  }),
});

Experimental joins

Enabling joins allows Better Auth to fetch related data in a single query rather than issuing multiple round-trips. Endpoints such as /get-session and /get-full-organization see 2-3x latency improvements.
auth.ts
export const auth = betterAuth({
  experimental: { joins: true },
});
Joins require Drizzle relations definitions in your schema. Run npx auth@latest generate with the latest CLI to produce a schema that includes them. You must also pass each relation through the adapter’s schema object.