> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/better-auth/better-auth/llms.txt
> Use this file to discover all available pages before exploring further.

# Drizzle ORM

> Connect Better Auth to your database using Drizzle ORM.

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

<Steps>
  <Step title="Install Drizzle ORM and the Better Auth adapter">
    <Tabs>
      <Tab title="npm">
        ```bash theme={null}
        npm install drizzle-orm drizzle-kit
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash theme={null}
        pnpm add drizzle-orm drizzle-kit
        ```
      </Tab>

      <Tab title="yarn">
        ```bash theme={null}
        yarn add drizzle-orm drizzle-kit
        ```
      </Tab>

      <Tab title="bun">
        ```bash theme={null}
        bun add drizzle-orm drizzle-kit
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure the adapter">
    Import `drizzleAdapter` from `better-auth/adapters/drizzle` and pass your Drizzle `db` instance:

    ```typescript title="auth.ts" theme={null}
    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"
      }),
    });
    ```
  </Step>
</Steps>

## Configuration options

The `drizzleAdapter` function accepts a Drizzle `db` instance and a config object:

| Option        | Type                          | Description                                                                                  |
| ------------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
| `provider`    | `"pg" \| "mysql" \| "sqlite"` | The database provider. Required.                                                             |
| `schema`      | `Record<string, any>`         | Override the auto-detected schema. Useful when table names differ from model names.          |
| `usePlural`   | `boolean`                     | Set to `true` when all your table exports use plural names (e.g. `users` instead of `user`). |
| `camelCase`   | `boolean`                     | Use camelCase for generated field names instead of snake\_case. Defaults to `false`.         |
| `debugLogs`   | `boolean`                     | Enable verbose adapter logging. Defaults to `false`.                                         |
| `transaction` | `boolean`                     | Wrap 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.

```bash theme={null}
npx auth@latest generate
```

After generating the schema file, apply it to your database with Drizzle Kit:

<Tabs>
  <Tab title="generate migration">
    ```bash theme={null}
    npx drizzle-kit generate
    ```
  </Tab>

  <Tab title="apply migration">
    ```bash theme={null}
    npx drizzle-kit migrate
    ```
  </Tab>
</Tabs>

## Schema examples

The tables below are what `npx auth@latest generate` produces for each provider.

<Tabs>
  <Tab title="PostgreSQL">
    ```typescript title="schema.ts" theme={null}
    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] }),
    }));
    ```
  </Tab>

  <Tab title="MySQL">
    ```typescript title="schema.ts" theme={null}
    import { relations } from "drizzle-orm";
    import {
      mysqlTable,
      varchar,
      text,
      timestamp,
      boolean,
      index,
    } from "drizzle-orm/mysql-core";

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

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

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

    export const verification = mysqlTable(
      "verification",
      {
        id: varchar("id", { length: 36 }).primaryKey(),
        identifier: varchar("identifier", { length: 255 }).notNull(),
        value: text("value").notNull(),
        expiresAt: timestamp("expires_at", { fsp: 3 }).notNull(),
        createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
        updatedAt: timestamp("updated_at", { fsp: 3 })
          .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] }),
    }));
    ```
  </Tab>

  <Tab title="SQLite">
    ```typescript title="schema.ts" theme={null}
    import { relations, sql } from "drizzle-orm";
    import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";

    export const user = sqliteTable("user", {
      id: text("id").primaryKey(),
      name: text("name").notNull(),
      email: text("email").notNull().unique(),
      emailVerified: integer("email_verified", { mode: "boolean" })
        .default(false)
        .notNull(),
      image: text("image"),
      createdAt: integer("created_at", { mode: "timestamp_ms" })
        .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
        .notNull(),
      updatedAt: integer("updated_at", { mode: "timestamp_ms" })
        .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
        .$onUpdate(() => new Date())
        .notNull(),
    });

    export const session = sqliteTable(
      "session",
      {
        id: text("id").primaryKey(),
        expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
        token: text("token").notNull().unique(),
        createdAt: integer("created_at", { mode: "timestamp_ms" })
          .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
          .notNull(),
        updatedAt: integer("updated_at", { mode: "timestamp_ms" })
          .$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 = sqliteTable(
      "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: integer("access_token_expires_at", {
          mode: "timestamp_ms",
        }),
        refreshTokenExpiresAt: integer("refresh_token_expires_at", {
          mode: "timestamp_ms",
        }),
        scope: text("scope"),
        password: text("password"),
        createdAt: integer("created_at", { mode: "timestamp_ms" })
          .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
          .notNull(),
        updatedAt: integer("updated_at", { mode: "timestamp_ms" })
          .$onUpdate(() => new Date())
          .notNull(),
      },
      (table) => [index("account_userId_idx").on(table.userId)],
    );

    export const verification = sqliteTable(
      "verification",
      {
        id: text("id").primaryKey(),
        identifier: text("identifier").notNull(),
        value: text("value").notNull(),
        expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
        createdAt: integer("created_at", { mode: "timestamp_ms" })
          .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
          .notNull(),
        updatedAt: integer("updated_at", { mode: "timestamp_ms" })
          .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
          .$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] }),
    }));
    ```
  </Tab>
</Tabs>

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

```typescript title="auth.ts" theme={null}
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:

```typescript title="auth.ts" theme={null}
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:

```typescript theme={null}
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:

```typescript title="auth.ts" theme={null}
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:

```typescript title="auth.ts" theme={null}
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.

```typescript title="auth.ts" theme={null}
export const auth = betterAuth({
  experimental: { joins: true },
});
```

<Warning>
  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.
</Warning>
