DocumentsBetter Auth
Drizzle Schema Generator
Drizzle Schema Generator
Type
Topic
Status
Published
Created
Jul 8, 2026
Updated
Jul 8, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Drizzle Schema Generator#

The Drizzle schema generator (packages/cli/src/generators/drizzle.ts) is the CLI component responsible for auto-generating Drizzle ORM table definitions from Better Auth's internal schema (BetterAuthDBSchema). It supports three database backends — pg, mysql, and sqlite — and emits fully-formatted TypeScript that can be dropped directly into a project.

Entry point#

generateDrizzleSchema is the exported SchemaGenerator function. It:

  1. Reads getAuthTables(options) to obtain the canonical schema .
  2. Resolves the target database type from adapter.options?.provider and throws if it's absent .
  3. Iterates every table, builds column definitions and collects indexes, then renders the final pgTable/mysqlTable/sqliteTable call .
  4. Appends Drizzle relations(...) exports for all foreign-key relationships .
  5. Runs Prettier on the output before returning .

Column-level vs. table-level constraints#

The generator uses two independent code paths to emit uniqueness and index constraints, and their interaction is the source of a known bug.

Column-level .unique()#

At the end of each field's column definition string, .unique() is unconditionally appended whenever attr.unique is truthy :

${attr.unique ? ".unique()" : ""}

Table-level index / uniqueIndex#

Separately, while mapping over fields, the generator decides whether to push a table-level index entry into the local indexes array :

Field flagsEmitted index type
index: true, unique: falseindex("…_idx")
index: true, unique: trueuniqueIndex("…_uidx")
index: false, unique: true (or false)(no table-level index)

These collected entries are later rendered as the second argument to the table constructor via assignIndexes.

Import generation#

generateImport mirrors this logic when deciding which symbols to import from drizzle-orm/${databaseType}-core. It checks field.index && !field.unique to decide whether to import index, and field.unique && field.index for uniqueIndex .


Bug: duplicate constraints when both unique and index are set#

Because the two code paths operate independently, a field with both unique: true and index: true triggers both:

  1. Column-level .unique() (enforces uniqueness inline on the column).
  2. Table-level uniqueIndex("…_uidx") (a separate, redundant unique index).

This results in two unique constraints on the same column in the generated schema — confirmed in GitHub issue #10332. A concrete example is organization.slug from the organization plugin, which carries both flags and thus produces:

// column definition
slug: text("slug").notNull().unique()

// table-level index (redundant)
uniqueIndex("organization_slug_uidx").on(table.slug)

Proposed fix#

The minimal fix is to suppress the column-level .unique() when a table-level uniqueIndex will already be emitted :

${attr.unique && !attr.index ? ".unique()" : ""}

This defers uniqueness enforcement exclusively to the table-level uniqueIndex whenever both flags are set, eliminating the duplicate constraint.


Key files and references#

ResourceLink
Generator sourcepackages/cli/src/generators/drizzle.ts
Bug report (issue #10332)
Column .unique() emission
Table-level index decision
assignIndexes helper
Import generation logic
Drizzle Schema Generator | Dosu