[GH-ISSUE #1027] ERROR [Better Auth]: column "emailVerified" does not exist #8556

Closed
opened 2026-04-13 03:40:22 -05:00 by GiteaMirror · 21 comments
Owner

Originally created by @thefrana on GitHub (Dec 26, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1027

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Use Drizzle Adapter with casing: snake_case.

Current vs. Expected behavior

When I try to sign up, it throws ERROR [Better Auth]: column "emailVerified" does not exist, but the column is in the schema and it does exist in the database. The problem may be that Drizzle automatically creates the name for its schema and Better Auth has problem with it.

My schema:

export const $user = pgTable("user", {
  ...,
  emailVerified: boolean().notNull(),
  ...
})

Even with Better Auth config, it still does not work:

  user: {
    modelName: "user",
    fields: {
      name: "name",
      email: "email",
      emailVerified: "email_verified",
      image: "image",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },
  },

What version of Better Auth are you using?

1.1.3

Provide environment information

- OS: MacOS
- NodeJS: v20

Which area(s) are affected? (Select all that apply)

Backend, Client

Auth config (if applicable)

import { betterAuth } from "better-auth"

export const auth = betterAuth({
  baseURL: ConfigKit.vars.baseUrl,
  secret: ConfigKit.vars.secret,
  emailAndPassword: {  
    enabled: true
  },
  database: drizzleAdapter(DbService, {
    provider: "pg",
    schema: {
      user: schema.$user,
      session: schema.$session,
      account: schema.$account,
      verification: schema.$verification,
    },
  }),

  // optionally without success
  user: {
    modelName: "user",
    fields: {
      name: "name",
      email: "email",
      emailVerified: "email_verified",
      image: "image",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },
  },
});

Additional context

No response

Originally created by @thefrana on GitHub (Dec 26, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1027 ### Is this suited for github? - [X] Yes, this is suited for github ### To Reproduce Use Drizzle Adapter with `casing: snake_case`. ### Current vs. Expected behavior When I try to sign up, it throws `ERROR [Better Auth]: column "emailVerified" does not exist`, but the column is in the schema and it does exist in the database. The problem may be that Drizzle automatically creates the name for its schema and Better Auth has problem with it. My schema: ``` export const $user = pgTable("user", { ..., emailVerified: boolean().notNull(), ... }) ``` Even with Better Auth config, it still does not work: ``` user: { modelName: "user", fields: { name: "name", email: "email", emailVerified: "email_verified", image: "image", createdAt: "created_at", updatedAt: "updated_at", }, }, ``` ### What version of Better Auth are you using? 1.1.3 ### Provide environment information ```bash - OS: MacOS - NodeJS: v20 ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ baseURL: ConfigKit.vars.baseUrl, secret: ConfigKit.vars.secret, emailAndPassword: { enabled: true }, database: drizzleAdapter(DbService, { provider: "pg", schema: { user: schema.$user, session: schema.$session, account: schema.$account, verification: schema.$verification, }, }), // optionally without success user: { modelName: "user", fields: { name: "name", email: "email", emailVerified: "email_verified", image: "image", createdAt: "created_at", updatedAt: "updated_at", }, }, }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 03:40:22 -05:00
Author
Owner

@thefrana commented on GitHub (Dec 26, 2024):

However, when I manually specify:

export const $user = pgTable("user", {
  ...,
  emailVerified: boolean('email_verified').notNull(),
  ...
})

it starts working, but it is weird.

<!-- gh-comment-id:2563000567 --> @thefrana commented on GitHub (Dec 26, 2024): However, when I manually specify: ``` export const $user = pgTable("user", { ..., emailVerified: boolean('email_verified').notNull(), ... }) ``` it starts working, but it is weird.
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

For Drizzle I had to remove this:

   fields: {
      name: "name",
      email: "email",
      emailVerified: "email_verified",
      image: "image",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },

When you use the Drizzle adapter you don't need to specify how they appear in SQL to auth, just provide the schema

import * as schema from "@/db/schema"

export const auth = betterAuth({
    database: drizzleAdapter(db, { provider: "pg", schema }),
    user: {
        modelName: "users",
    }
})
export const users = pgTable("users", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	name: text().notNull(),
	email: text().notNull(),
	emailVerified: boolean("email_verified").notNull(),
	image: text(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
	isAnonymous: boolean("is_anonymous"),
}, (table) => [
	unique("users_email_unique").on(table.email),
])
<!-- gh-comment-id:2563044915 --> @daveycodez commented on GitHub (Dec 26, 2024): For Drizzle I had to remove this: ```ts fields: { name: "name", email: "email", emailVerified: "email_verified", image: "image", createdAt: "created_at", updatedAt: "updated_at", }, ``` When you use the Drizzle adapter you don't need to specify how they appear in SQL to auth, just provide the schema ```ts import * as schema from "@/db/schema" export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema }), user: { modelName: "users", } }) ``` ```ts export const users = pgTable("users", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), name: text().notNull(), email: text().notNull(), emailVerified: boolean("email_verified").notNull(), image: text(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), isAnonymous: boolean("is_anonymous"), }, (table) => [ unique("users_email_unique").on(table.email), ]) ```
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

In your case modelName is user so you can remove your entire user object

<!-- gh-comment-id:2563047112 --> @daveycodez commented on GitHub (Dec 26, 2024): In your case modelName is user so you can remove your entire user object
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

Actually I think you would put modelName: "$user" ? I'm not sure where the dollar sign is coming from, my generations haven't given me that from better-auth cli or drizzle-kit pull

<!-- gh-comment-id:2563049831 --> @daveycodez commented on GitHub (Dec 26, 2024): Actually I think you would put modelName: "$user" ? I'm not sure where the dollar sign is coming from, my generations haven't given me that from better-auth cli or drizzle-kit pull
Author
Owner

@thefrana commented on GitHub (Dec 26, 2024):

The dollar sign is just our convention so we do not confuse instance of the row and the table schema.

Upon more testing, the problem seems to be that you have to specify column name when the column name is more than one word:

emailVerified: boolean("email_verified").notNull(), // column name specified

However, it is perfectly valid to use this in Drizzle, but it is not accepted by Better Auth:

emailVerified: boolean().notNull(), // column name omitted

I strongly believe this is a bug in Better Auth, because the generated database is the same in both cases and the column name can be inferred according to casing: "snake_case" option in Drizzle config.

<!-- gh-comment-id:2563147716 --> @thefrana commented on GitHub (Dec 26, 2024): The dollar sign is just our convention so we do not confuse instance of the row and the table schema. Upon more testing, the problem seems to be that you **have** to specify column name when the column name is more than one word: ```ts emailVerified: boolean("email_verified").notNull(), // column name specified ``` However, it is perfectly valid to use this in Drizzle, but it is not accepted by Better Auth: ```ts emailVerified: boolean().notNull(), // column name omitted ``` I strongly believe this is a bug in Better Auth, because the generated database is the same in both cases and the column name can be inferred according to `casing: "snake_case"` option in Drizzle config.
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

Did you try passing the entire Schema object and then using modelName with the $:

database: drizzleAdapter(DbService, {
    provider: "pg",
    schema: schema,
  }),
user: {
    modelName: "$user"
}
<!-- gh-comment-id:2563167648 --> @daveycodez commented on GitHub (Dec 26, 2024): Did you try passing the entire Schema object and then using modelName with the $: ```ts database: drizzleAdapter(DbService, { provider: "pg", schema: schema, }), ``` ```ts user: { modelName: "$user" } ```
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

This is what I have working atm for Drizzle with plural tables & snake_case columns with latest version of Better Auth. I think modelName maps to the export name

auth.ts

import { betterAuth } from "better-auth"
import { anonymous, jwt, magicLink } from "better-auth/plugins"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { db } from "@/db/database"
import * as schema from "@/db/schema"
import { Resend } from "resend"
import { EmailTemplate } from "@/components/email-template"
const resend = new Resend(process.env.RESEND_API_KEY)

export const auth = betterAuth({
    database: drizzleAdapter(db, { provider: "pg", schema }),
    emailAndPassword: {
        enabled: true,
    },
    user: {
        modelName: "users"
    },
    session: {
        modelName: "sessions",
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // Cache duration in seconds
        }
    },
    account: {
        modelName: "accounts",
    },
    verification: {
        modelName: "verifications",
    }
})

export const {
    getJwks,
    getSession,
    getToken
} = auth.api

schema.ts

import { pgTable, foreignKey, uuid, text, timestamp, unique, boolean } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"

export const users = pgTable("users", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	name: text().notNull(),
	email: text().notNull(),
	emailVerified: boolean("email_verified").notNull(),
	image: text(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
	isAnonymous: boolean("is_anonymous"),
}, (table) => [
	unique("users_email_unique").on(table.email),
])

export const jwks = pgTable("jwks", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	publicKey: text("public_key").notNull(),
	privateKey: text("private_key").notNull(),
	createdAt: timestamp("created_at").notNull(),
})

export const sessions = pgTable("sessions", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	expiresAt: timestamp("expires_at").notNull(),
	token: text().notNull(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
	ipAddress: text("ip_address"),
	userAgent: text("user_agent"),
	userId: uuid("user_id").notNull(),
}, (table) => [
	foreignKey({
		columns: [table.userId],
		foreignColumns: [users.id],
		name: "sessions_user_id_users_id_fk"
	}).onDelete("cascade"),
	unique("sessions_token_unique").on(table.token),
])

export const verifications = pgTable("verifications", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	identifier: text().notNull(),
	value: text().notNull(),
	expiresAt: timestamp("expires_at").notNull(),
	createdAt: timestamp("created_at"),
	updatedAt: timestamp("updated_at"),
})

export const accounts = pgTable("accounts", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	accountId: text("account_id").notNull(),
	providerId: text("provider_id").notNull(),
	userId: uuid("user_id").notNull(),
	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(),
	password: text(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
}, (table) => [
	foreignKey({
		columns: [table.userId],
		foreignColumns: [users.id],
		name: "accounts_user_id_users_id_fk"
	}).onDelete("cascade"),
])
<!-- gh-comment-id:2563168137 --> @daveycodez commented on GitHub (Dec 26, 2024): This is what I have working atm for Drizzle with plural tables & snake_case columns with latest version of Better Auth. I think modelName maps to the export name # auth.ts ```ts import { betterAuth } from "better-auth" import { anonymous, jwt, magicLink } from "better-auth/plugins" import { drizzleAdapter } from "better-auth/adapters/drizzle" import { db } from "@/db/database" import * as schema from "@/db/schema" import { Resend } from "resend" import { EmailTemplate } from "@/components/email-template" const resend = new Resend(process.env.RESEND_API_KEY) export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema }), emailAndPassword: { enabled: true, }, user: { modelName: "users" }, session: { modelName: "sessions", cookieCache: { enabled: true, maxAge: 5 * 60 // Cache duration in seconds } }, account: { modelName: "accounts", }, verification: { modelName: "verifications", } }) export const { getJwks, getSession, getToken } = auth.api ``` # schema.ts ```ts import { pgTable, foreignKey, uuid, text, timestamp, unique, boolean } from "drizzle-orm/pg-core" import { sql } from "drizzle-orm" export const users = pgTable("users", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), name: text().notNull(), email: text().notNull(), emailVerified: boolean("email_verified").notNull(), image: text(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), isAnonymous: boolean("is_anonymous"), }, (table) => [ unique("users_email_unique").on(table.email), ]) export const jwks = pgTable("jwks", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), publicKey: text("public_key").notNull(), privateKey: text("private_key").notNull(), createdAt: timestamp("created_at").notNull(), }) export const sessions = pgTable("sessions", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), expiresAt: timestamp("expires_at").notNull(), token: text().notNull(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: uuid("user_id").notNull(), }, (table) => [ foreignKey({ columns: [table.userId], foreignColumns: [users.id], name: "sessions_user_id_users_id_fk" }).onDelete("cascade"), unique("sessions_token_unique").on(table.token), ]) export const verifications = pgTable("verifications", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), identifier: text().notNull(), value: text().notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at"), updatedAt: timestamp("updated_at"), }) export const accounts = pgTable("accounts", { id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: uuid("user_id").notNull(), 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(), password: text(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), }, (table) => [ foreignKey({ columns: [table.userId], foreignColumns: [users.id], name: "accounts_user_id_users_id_fk" }).onDelete("cascade"), ]) ```
Author
Owner

@benpixel commented on GitHub (Jan 23, 2025):

Upon more testing, the problem seems to be that you have to specify column name when the column name is more than one word:

emailVerified: boolean("email_verified").notNull(), // column name specified
However, it is perfectly valid to use this in Drizzle, but it is not accepted by Better Auth:

emailVerified: boolean().notNull(), // column name omitted
I strongly believe this is a bug in Better Auth, because the generated database is the same in both cases and the column name can be inferred according to casing: "snake_case" option in Drizzle config.

@thefrana you're right!

I'm experiencing the same error and the problem goes away when I explicitly specify column names.

Manually specifying fields in better-auth config doesn't seem change anything.

<!-- gh-comment-id:2610573636 --> @benpixel commented on GitHub (Jan 23, 2025): > Upon more testing, the problem seems to be that you **have** to specify column name when the column name is more than one word: > > emailVerified: boolean("email_verified").notNull(), // column name specified > However, it is perfectly valid to use this in Drizzle, but it is not accepted by Better Auth: > > emailVerified: boolean().notNull(), // column name omitted > I strongly believe this is a bug in Better Auth, because the generated database is the same in both cases and the column name can be inferred according to `casing: "snake_case"` option in Drizzle config. @thefrana you're right! I'm experiencing the same error and the problem goes away when I explicitly specify column names. Manually specifying fields in better-auth config doesn't seem change anything.
Author
Owner

@benpixel commented on GitHub (Feb 13, 2025):

This seems to be fixed now.

<!-- gh-comment-id:2657399474 --> @benpixel commented on GitHub (Feb 13, 2025): This seems to be fixed now.
Author
Owner

@thefrana commented on GitHub (Feb 13, 2025):

I will try it and leave a comment if that is true. Thanks for notice

<!-- gh-comment-id:2657788109 --> @thefrana commented on GitHub (Feb 13, 2025): I will try it and leave a comment if that is true. Thanks for notice
Author
Owner

@w3Scribe commented on GitHub (Mar 12, 2025):

This is what I have working atm for Drizzle with plural tables & snake_case columns with latest version of Better Auth. I think modelName maps to the export name

auth.ts

import { betterAuth } from "better-auth"
import { anonymous, jwt, magicLink } from "better-auth/plugins"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { db } from "@/db/database"
import * as schema from "@/db/schema"
import { Resend } from "resend"
import { EmailTemplate } from "@/components/email-template"
const resend = new Resend(process.env.RESEND_API_KEY)

export const auth = betterAuth({
    database: drizzleAdapter(db, { provider: "pg", schema }),
    emailAndPassword: {
        enabled: true,
    },
    user: {
        modelName: "users"
    },
    session: {
        modelName: "sessions",
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // Cache duration in seconds
        }
    },
    account: {
        modelName: "accounts",
    },
    verification: {
        modelName: "verifications",
    }
})

export const {
    getJwks,
    getSession,
    getToken
} = auth.api

schema.ts

import { pgTable, foreignKey, uuid, text, timestamp, unique, boolean } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"

export const users = pgTable("users", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	name: text().notNull(),
	email: text().notNull(),
	emailVerified: boolean("email_verified").notNull(),
	image: text(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
	isAnonymous: boolean("is_anonymous"),
}, (table) => [
	unique("users_email_unique").on(table.email),
])

export const jwks = pgTable("jwks", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	publicKey: text("public_key").notNull(),
	privateKey: text("private_key").notNull(),
	createdAt: timestamp("created_at").notNull(),
})

export const sessions = pgTable("sessions", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	expiresAt: timestamp("expires_at").notNull(),
	token: text().notNull(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
	ipAddress: text("ip_address"),
	userAgent: text("user_agent"),
	userId: uuid("user_id").notNull(),
}, (table) => [
	foreignKey({
		columns: [table.userId],
		foreignColumns: [users.id],
		name: "sessions_user_id_users_id_fk"
	}).onDelete("cascade"),
	unique("sessions_token_unique").on(table.token),
])

export const verifications = pgTable("verifications", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	identifier: text().notNull(),
	value: text().notNull(),
	expiresAt: timestamp("expires_at").notNull(),
	createdAt: timestamp("created_at"),
	updatedAt: timestamp("updated_at"),
})

export const accounts = pgTable("accounts", {
	id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
	accountId: text("account_id").notNull(),
	providerId: text("provider_id").notNull(),
	userId: uuid("user_id").notNull(),
	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(),
	password: text(),
	createdAt: timestamp("created_at").notNull(),
	updatedAt: timestamp("updated_at").notNull(),
}, (table) => [
	foreignKey({
		columns: [table.userId],
		foreignColumns: [users.id],
		name: "accounts_user_id_users_id_fk"
	}).onDelete("cascade"),
])

Does this work?
bunx @better-auth/cli@latest generate

<!-- gh-comment-id:2719236981 --> @w3Scribe commented on GitHub (Mar 12, 2025): > This is what I have working atm for Drizzle with plural tables & snake_case columns with latest version of Better Auth. I think modelName maps to the export name > > # auth.ts > ```ts > import { betterAuth } from "better-auth" > import { anonymous, jwt, magicLink } from "better-auth/plugins" > import { drizzleAdapter } from "better-auth/adapters/drizzle" > import { db } from "@/db/database" > import * as schema from "@/db/schema" > import { Resend } from "resend" > import { EmailTemplate } from "@/components/email-template" > const resend = new Resend(process.env.RESEND_API_KEY) > > export const auth = betterAuth({ > database: drizzleAdapter(db, { provider: "pg", schema }), > emailAndPassword: { > enabled: true, > }, > user: { > modelName: "users" > }, > session: { > modelName: "sessions", > cookieCache: { > enabled: true, > maxAge: 5 * 60 // Cache duration in seconds > } > }, > account: { > modelName: "accounts", > }, > verification: { > modelName: "verifications", > } > }) > > export const { > getJwks, > getSession, > getToken > } = auth.api > ``` > > # schema.ts > ```ts > import { pgTable, foreignKey, uuid, text, timestamp, unique, boolean } from "drizzle-orm/pg-core" > import { sql } from "drizzle-orm" > > export const users = pgTable("users", { > id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), > name: text().notNull(), > email: text().notNull(), > emailVerified: boolean("email_verified").notNull(), > image: text(), > createdAt: timestamp("created_at").notNull(), > updatedAt: timestamp("updated_at").notNull(), > isAnonymous: boolean("is_anonymous"), > }, (table) => [ > unique("users_email_unique").on(table.email), > ]) > > export const jwks = pgTable("jwks", { > id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), > publicKey: text("public_key").notNull(), > privateKey: text("private_key").notNull(), > createdAt: timestamp("created_at").notNull(), > }) > > export const sessions = pgTable("sessions", { > id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), > expiresAt: timestamp("expires_at").notNull(), > token: text().notNull(), > createdAt: timestamp("created_at").notNull(), > updatedAt: timestamp("updated_at").notNull(), > ipAddress: text("ip_address"), > userAgent: text("user_agent"), > userId: uuid("user_id").notNull(), > }, (table) => [ > foreignKey({ > columns: [table.userId], > foreignColumns: [users.id], > name: "sessions_user_id_users_id_fk" > }).onDelete("cascade"), > unique("sessions_token_unique").on(table.token), > ]) > > export const verifications = pgTable("verifications", { > id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), > identifier: text().notNull(), > value: text().notNull(), > expiresAt: timestamp("expires_at").notNull(), > createdAt: timestamp("created_at"), > updatedAt: timestamp("updated_at"), > }) > > export const accounts = pgTable("accounts", { > id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(), > accountId: text("account_id").notNull(), > providerId: text("provider_id").notNull(), > userId: uuid("user_id").notNull(), > 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(), > password: text(), > createdAt: timestamp("created_at").notNull(), > updatedAt: timestamp("updated_at").notNull(), > }, (table) => [ > foreignKey({ > columns: [table.userId], > foreignColumns: [users.id], > name: "accounts_user_id_users_id_fk" > }).onDelete("cascade"), > ]) > ``` Does this work? bunx @better-auth/cli@latest generate
Author
Owner

@daveycodez commented on GitHub (Mar 12, 2025):

It should just work with usePlurals, though, shouldn't need to manually write the modelNames

<!-- gh-comment-id:2719256614 --> @daveycodez commented on GitHub (Mar 12, 2025): It should just work with usePlurals, though, shouldn't need to manually write the modelNames
Author
Owner

@Abhishek21k commented on GitHub (May 25, 2025):

verification table:

export const verificationCodesTable = pgTable("verification_codes", {
  id: uuid("id").primaryKey(),
  identifier: varchar("identifier", { length: 255 }).notNull(),
  value: varchar("value", { length: 255 }).notNull(),
  type: varchar("type", { length: 50 }).notNull(),
  expiresAt: timestamp("expires_at").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
  userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }),
  attempts: integer("attempts").default(0).notNull(),
  isUsed: boolean("is_used").default(false).notNull(),
});

in my auth.ts adaptor i'm passing the table as this:

  database: drizzleAdapter(db, {
    provider: "pg", 
    schema: {
      user: schema.users,
      session: schema.sessionsTable,
      account: schema.accountsTable, 
      verification: schema.verificationCodesTable,
    },
    usePlural: false, 
  }),

  verification: {
    modelName: "verification_codes",
    fields: {
      identifier: "identifier",
      value: "value",
      expiresAt: "expires_at",
      createdAt: "created_at",
      updatedAt: "updated_at",
      userId: "user_id",
    },
  },

and getting this error:

# SERVER_ERROR:  [Error [BetterAuthError]: [# Drizzle Adapter]: The model "verification_codes" was not found in the schema object. Please pass the schema directly to the adapter options.] {
  cause: undefined
}
 POST /api/auth/email-otp/send-verification-otp 500 in 391ms
<!-- gh-comment-id:2907707565 --> @Abhishek21k commented on GitHub (May 25, 2025): verification table: ```typescript export const verificationCodesTable = pgTable("verification_codes", { id: uuid("id").primaryKey(), identifier: varchar("identifier", { length: 255 }).notNull(), value: varchar("value", { length: 255 }).notNull(), type: varchar("type", { length: 50 }).notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }), attempts: integer("attempts").default(0).notNull(), isUsed: boolean("is_used").default(false).notNull(), }); ``` in my auth.ts adaptor i'm passing the table as this: ```typescript database: drizzleAdapter(db, { provider: "pg", schema: { user: schema.users, session: schema.sessionsTable, account: schema.accountsTable, verification: schema.verificationCodesTable, }, usePlural: false, }), verification: { modelName: "verification_codes", fields: { identifier: "identifier", value: "value", expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at", userId: "user_id", }, }, ``` and getting this error: ```error # SERVER_ERROR: [Error [BetterAuthError]: [# Drizzle Adapter]: The model "verification_codes" was not found in the schema object. Please pass the schema directly to the adapter options.] { cause: undefined } POST /api/auth/email-otp/send-verification-otp 500 in 391ms ```
Author
Owner

@Abhishek21k commented on GitHub (May 25, 2025):

it was fixed if i also gave the adaptor the drizzle verificationCodesTable variable name also,

  database: drizzleAdapter(db, {
    provider: "pg", 
    schema: {
      user: schema.users,
      session: schema.sessionsTable,
      account: schema.accountsTable, 
      verification: schema.verificationCodesTable,
      verification_codes: schema.verificationCodesTable,
    },
    usePlural: false, 
  }),

it seems the variable name and the db table name are not been mapped correctly

<!-- gh-comment-id:2907719137 --> @Abhishek21k commented on GitHub (May 25, 2025): it was fixed if i also gave the adaptor the drizzle verificationCodesTable variable name also, ```typescript database: drizzleAdapter(db, { provider: "pg", schema: { user: schema.users, session: schema.sessionsTable, account: schema.accountsTable, verification: schema.verificationCodesTable, verification_codes: schema.verificationCodesTable, }, usePlural: false, }), ``` it seems the variable name and the db table name are not been mapped correctly
Author
Owner

@Quadrubo commented on GitHub (Jun 26, 2025):

I am having the same issue while using kysely.

This is my config:

export const dialect = new PostgresDialect({
  pool: new Pool({
    connectionString: process.env.DATABASE_URL,
  }),
});

export const db = new Kysely<DB>({
  dialect: dialect,
});

export const auth = betterAuth({
  database: {
    dialect: dialect,
    type: "postgres",
    casing: "snake",
    debugLogs: true,
  },
...

However casing doesn't seem to do anything, This is my db log, it tries to find the column emailVerified in camelCase.

db-1  | 2025-06-26 11:21:33.881 UTC [74] STATEMENT:  insert into "user" ("name", "email", "emailVerified", "createdAt", "updatedAt", "id") values ($1, $2, $3, $4, $5, $6) returning *
<!-- gh-comment-id:3008147901 --> @Quadrubo commented on GitHub (Jun 26, 2025): I am having the same issue while using kysely. This is my config: ```ts export const dialect = new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL, }), }); export const db = new Kysely<DB>({ dialect: dialect, }); export const auth = betterAuth({ database: { dialect: dialect, type: "postgres", casing: "snake", debugLogs: true, }, ... ``` However casing doesn't seem to do anything, This is my db log, it tries to find the column `emailVerified` in camelCase. ```shell db-1 | 2025-06-26 11:21:33.881 UTC [74] STATEMENT: insert into "user" ("name", "email", "emailVerified", "createdAt", "updatedAt", "id") values ($1, $2, $3, $4, $5, $6) returning * ```
Author
Owner

@rebasecase commented on GitHub (Jun 28, 2025):

@Quadrubo I had to manually map them, it seems casing doesn't do anything e.g.

  user: {
    fields: {
      emailVerified: 'email_verified',
...
<!-- gh-comment-id:3015206756 --> @rebasecase commented on GitHub (Jun 28, 2025): @Quadrubo I had to manually map them, it seems `casing` doesn't do anything e.g. ``` user: { fields: { emailVerified: 'email_verified', ... ```
Author
Owner

@ping-maxwell commented on GitHub (Jul 8, 2025):

Hey all, just wanted to address a few things:

  • As you guys have discovered (or I believe so at least), the Drizzle adapter doesn't use the table names you may have written, it uses the schema object keys to represent table names.
  • The kysely adapter for @Quadrubo 's a separate issue to this, and @rebasecase is right in that casing doesn't do anything
  • I wouldn't recommend passing usePlural for the drizzle adapter if you're going to define the schema to include the plural in each key name yourself

If you guys have any other questions please tag me.
In the mean time I'll close this issue - if this is still a valid issue I'm happy to just re-open this!

<!-- gh-comment-id:3049109655 --> @ping-maxwell commented on GitHub (Jul 8, 2025): Hey all, just wanted to address a few things: * As you guys have discovered (or I believe so at least), the Drizzle adapter doesn't use the table names you may have written, it uses the schema object keys to represent table names. * The kysely adapter for @Quadrubo 's a separate issue to this, and @rebasecase is right in that casing doesn't do anything * I wouldn't recommend passing `usePlural` for the drizzle adapter if you're going to define the schema to include the plural in each key name yourself If you guys have any other questions please tag me. In the mean time I'll close this issue - if this is still a valid issue I'm happy to just re-open this!
Author
Owner

@austinm911 commented on GitHub (Aug 24, 2025):

@ping-maxwell this is still a footgun that should be addressed. I just spent an hour trying to figure out why my queries weren't working.

Even if you set the fields param to manually map camelCase to snake_case in the auth instance, you will still get wrong queries/mutations that are sent as camelCase.

That is unless you also map the drizzle schema manually with the snakeCase string.

The schema below shows this. Notice createdAt is an issue because it's not manually written in the drizzle schema. This behavior is unexpected as you can configure Drizzle without manually naming the db side table names and using casing: snake_case in your drizzle.config.ts to handle this automatic mapping.

  query: 'select "id", "name", "email", "email_verified", "image", "createdAt", "updatedAt" from "users" where "users"."email" = $1',
  params: [ 'test@email.com' ],
  cause: error: column "createdAt" does not exist
export const users = pgTable('users', {
	id: text().primaryKey(),
	name: text().notNull(),
	email: text().notNull().unique(),
    // manual snake_case 
	emailVerified: boolean('email_verified').notNull(),
	image: text(),
    // no manual snake_case 
	createdAt: timestamp({ withTimezone: true }).notNull(),
	updatedAt: timestamp({ withTimezone: true }),
})

export const auth = betterAuth({
	database: drizzleAdapter(db, {
		provider: 'pg',
		schema,
		debugLogs: true,
	}),
	user: {
		modelName: 'users',
		fields: {
			createdAt: 'created_at',
			updatedAt: 'updated_at',
			emailVerified: 'email_verified',
		},
	},

	},
})

If plural isn't relevant to Drizzle Adapter I think adding a comment to the JSDOC would be helpful. I spent some trying using that option thinking it was the issue.

<!-- gh-comment-id:3217849276 --> @austinm911 commented on GitHub (Aug 24, 2025): @ping-maxwell this is still a footgun that should be addressed. I just spent an hour trying to figure out why my queries weren't working. Even if you set the `fields` param to manually map camelCase to snake_case in the auth instance, you will still get wrong queries/mutations that are sent as camelCase. That is **unless** you also map the drizzle schema manually with the snakeCase string. The schema below shows this. Notice `createdAt` is an issue because it's not manually written in the drizzle schema. This behavior is unexpected as you can configure Drizzle without manually naming the db side table names and using `casing: snake_case` in your `drizzle.config.ts` to handle this automatic mapping. ```bash query: 'select "id", "name", "email", "email_verified", "image", "createdAt", "updatedAt" from "users" where "users"."email" = $1', params: [ 'test@email.com' ], cause: error: column "createdAt" does not exist ``` ```ts export const users = pgTable('users', { id: text().primaryKey(), name: text().notNull(), email: text().notNull().unique(), // manual snake_case emailVerified: boolean('email_verified').notNull(), image: text(), // no manual snake_case createdAt: timestamp({ withTimezone: true }).notNull(), updatedAt: timestamp({ withTimezone: true }), }) export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema, debugLogs: true, }), user: { modelName: 'users', fields: { createdAt: 'created_at', updatedAt: 'updated_at', emailVerified: 'email_verified', }, }, }, }) ``` If `plural` isn't relevant to Drizzle Adapter I think adding a comment to the JSDOC would be helpful. I spent some trying using that option thinking it was the issue.
Author
Owner

@Quadrubo commented on GitHub (Aug 28, 2025):

@ping-maxwell How would I go about remapping for plugins such as two factor?
The remapping works for now for the default tables, however I can't get it to work in the two factor plugin.

I would like my column names to be consistent between tables.

<!-- gh-comment-id:3233586900 --> @Quadrubo commented on GitHub (Aug 28, 2025): @ping-maxwell How would I go about remapping for plugins such as two factor? The remapping works for now for the default tables, however I can't get it to work in the two factor plugin. I would like my column names to be consistent between tables.
Author
Owner

@ping-maxwell commented on GitHub (Aug 28, 2025):

@ping-maxwell How would I go about remapping for plugins such as two factor?
The remapping works for now for the default tables, however I can't get it to work in the two factor plugin.
I would like my column names to be consistent between tables.

Plugins that introduce a new schema often will allow you to configure it's schema within the plugin options.
Have you checked the twoFactor plugin options in your IDE to find a schema property?

<!-- gh-comment-id:3234230755 --> @ping-maxwell commented on GitHub (Aug 28, 2025): > @ping-maxwell How would I go about remapping for plugins such as two factor? The remapping works for now for the default tables, however I can't get it to work in the two factor plugin. I would like my column names to be consistent between tables. Plugins that introduce a new schema often will allow you to configure it's schema within the plugin options. Have you checked the `twoFactor` plugin options in your IDE to find a `schema` property?
Author
Owner

@Quadrubo commented on GitHub (Sep 2, 2025):

Plugins that introduce a new schema often will allow you to configure it's schema within the plugin options. Have you checked the twoFactor plugin options in your IDE to find a schema property?

Ah thank you, I managed now. Seems like all plugins I use provide this.

<!-- gh-comment-id:3244404715 --> @Quadrubo commented on GitHub (Sep 2, 2025): > Plugins that introduce a new schema often will allow you to configure it's schema within the plugin options. Have you checked the `twoFactor` plugin options in your IDE to find a `schema` property? Ah thank you, I managed now. Seems like all plugins I use provide this.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#8556