[GH-ISSUE #410] add casing option #25580

Closed
opened 2026-04-17 15:51:06 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @AliMejbar on GitHub (Nov 3, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/410

Is your feature request related to a problem? Please describe.
It's tiring to rename the fields one by one when using another casing than camelCase or when having to migrate from another auth provider.

Describe the solution you'd like
To have an option to choose the casing (most of us use snake_case with SQL and particularly PostgreSQL) in the same way as Drizzle. It could look like this :

type Casing = "camelCase" | "snake_case" | ... | undefined
----
export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg',
    casing : "snake_case"
  })
  ...
}),

Describe alternatives you've considered
Continuing to rename all columns by hand.

Originally created by @AliMejbar on GitHub (Nov 3, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/410 **Is your feature request related to a problem? Please describe.** It's tiring to rename the fields one by one when using another casing than camelCase or when having to migrate from another auth provider. **Describe the solution you'd like** To have an option to choose the casing (most of us use snake_case with SQL and particularly PostgreSQL) in the same way as Drizzle. It could look like this : ```ts type Casing = "camelCase" | "snake_case" | ... | undefined ---- export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', casing : "snake_case" }) ... }), ``` **Describe alternatives you've considered** Continuing to rename all columns by hand.
GiteaMirror added the lockedenhancement labels 2026-04-17 15:51:06 -05:00
Author
Owner

@Bekacru commented on GitHub (Dec 2, 2024):

Casing should be handled by Drizzle itself, rather than by Better Auth. We’re considering adding casing support for the built-in Kysely plugin, but there are no plans for Drizzle or Prisma, casing should be managed in the ORM layer.

<!-- gh-comment-id:2512200066 --> @Bekacru commented on GitHub (Dec 2, 2024): Casing should be handled by Drizzle itself, rather than by Better Auth. We’re considering adding casing support for the built-in Kysely plugin, but there are no plans for Drizzle or Prisma, casing should be managed in the ORM layer.
Author
Owner

@TinsFox commented on GitHub (Dec 18, 2024):

Hello @Bekacru

I have a question, what does it mean that it should be handled by Drizzle itself, can you explain it in more detail? As far as I understand, this is the schema file generated by CLI. I looked at the source code of cli and it seems to be here https://github.com/better-auth/better-auth/blob/main/packages/cli/src /generators/drizzle.ts#L62-L80 We can just add a judgment to handle it. Is my understanding correct?

<!-- gh-comment-id:2551203654 --> @TinsFox commented on GitHub (Dec 18, 2024): Hello @Bekacru I have a question, what does it mean that it should be handled by Drizzle itself, can you explain it in more detail? As far as I understand, this is the schema file generated by CLI. I looked at the source code of cli and it seems to be here https://github.com/better-auth/better-auth/blob/main/packages/cli/src /generators/drizzle.ts#L62-L80 We can just add a judgment to handle it. Is my understanding correct?
Author
Owner

@Bekacru commented on GitHub (Dec 18, 2024):

Hello @Bekacru

I have a question, what does it mean that it should be handled by Drizzle itself, can you explain it in more detail? As far as I understand, this is the schema file generated by CLI. I looked at the source code of cli and it seems to be here https://github.com/better-auth/better-auth/blob/main/packages/cli/src /generators/drizzle.ts#L62-L80 We can just add a judgment to handle it. Is my understanding correct?

Hey, yeah my initial understanding of the issue was related to runtime case. for handling a schema where tableName is expected to be interpreted as table_name by Better Auth. But, supporting snake case in the CLI makes sense, and we should actually default to snake_case since it’s the more of convention.

<!-- gh-comment-id:2551443763 --> @Bekacru commented on GitHub (Dec 18, 2024): > Hello @Bekacru > > I have a question, what does it mean that it should be handled by Drizzle itself, can you explain it in more detail? As far as I understand, this is the schema file generated by CLI. I looked at the source code of cli and it seems to be here https://github.com/better-auth/better-auth/blob/main/packages/cli/src /generators/drizzle.ts#L62-L80 We can just add a judgment to handle it. Is my understanding correct? Hey, yeah my initial understanding of the issue was related to runtime case. for handling a schema where `tableName` is expected to be interpreted as `table_name` by Better Auth. But, supporting snake case in the CLI makes sense, and we should actually default to `snake_case` since it’s the more of convention.
Author
Owner

@TinsFox commented on GitHub (Dec 19, 2024):

@Bekacru I think I see what you mean, JavaScript/TypeScript favors camelCase named variables in code, and I agree! But when designing the database, snake_case may be better. Using camelCase for database fields may cause problems (I learned this from other places, I have not studied this in depth and have not encountered such problems)

As you said, use snake_case in the database. But when better-auth or better-auth users use camelCase to obtain relevant data, this can be handled by the ORM.

Take drizzle as an example, this is the original

export const user = pgTable("user", {
...
createdAt: timestamp('createdAt').notNull()
});

When we modify it like this

export const user = pgTable("user", {
...
createdAt: timestamp('created_at').notNull()
});

We can still use user.createdAt to call data, and the ORM will convert the mapping during use.

Finally, if snake_case is a more suitable choice, I think we should deal with it as soon as possible and form a specification for it.

One problem with next-auth is that snake_case and camelCase are used interchangeably, which is crazy

image
<!-- gh-comment-id:2552653809 --> @TinsFox commented on GitHub (Dec 19, 2024): @Bekacru I think I see what you mean, JavaScript/TypeScript favors camelCase named variables in code, and I agree! But when designing the database, snake_case may be better. Using camelCase for database fields may cause problems (I learned this from other places, I have not studied this in depth and have not encountered such problems) As you said, use snake_case in the database. But when better-auth or better-auth users use camelCase to obtain relevant data, this can be handled by the ORM. Take drizzle as an example, this is the original ``` export const user = pgTable("user", { ... createdAt: timestamp('createdAt').notNull() }); ``` When we modify it like this ``` export const user = pgTable("user", { ... createdAt: timestamp('created_at').notNull() }); ``` We can still use user.createdAt to call data, and the ORM will convert the mapping during use. Finally, if snake_case is a more suitable choice, I think we should deal with it as soon as possible and form a specification for it. One problem with next-auth is that snake_case and camelCase are used interchangeably, which is crazy <img width="674" alt="image" src="https://github.com/user-attachments/assets/0b1b356a-9228-4ec8-a6cb-82c3112f42e6" />
Author
Owner

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

Not gonna lie guys I used ChatGPT and I copied the entire documentation page of the Database info and this is the output for your easy use. This is my current implementation for snake_case with Drizzle last night. I'm on my phone rn I'll add formatting and code block later.

import { betterAuth } from "better-auth"

export const auth = betterAuth({
 user: {
  modelName: "users",
  fields: {
   name: "name",
   email: "email",
   emailVerified: "email_verified",
   image: "image",
   createdAt: "created_at",
   updatedAt: "updated_at"
  }
 },
 session: {
  modelName: "sessions",
  fields: {
   userId: "user_id",
   token: "token",
   expiresAt: "expires_at",
   ipAddress: "ip_address",
   userAgent: "user_agent",
   createdAt: "created_at",
   updatedAt: "updated_at"
  }
 },
 account: {
  modelName: "accounts",
  fields: {
   userId: "user_id",
   accountId: "account_id",
   providerId: "provider_id",
   accessToken: "access_token",
   refreshToken: "refresh_token",
   accessTokenExpiresAt: "access_token_expires_at",
   refreshTokenExpiresAt: "refresh_token_expires_at",
   scope: "scope",
   password: "password",
   createdAt: "created_at",
   updatedAt: "updated_at"
  }
 },
 verification: {
  modelName: "verifications",
  fields: {
   identifier: "identifier",
   value: "value",
   expiresAt: "expires_at",
   createdAt: "created_at",
   updatedAt: "updated_at"
  }
 }
})





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

export const users = pgTable("users", {
 id: text("id").primaryKey(),
 name: text("name").notNull(),
 email: text("email").notNull().unique(),
 email_verified: boolean("email_verified").notNull(),
 image: text("image"),
 created_at: timestamp("created_at").notNull(),
 updated_at: timestamp("updated_at").notNull()
})

export const sessions = pgTable("sessions", {
 id: text("id").primaryKey(),
 expires_at: timestamp("expires_at").notNull(),
 token: text("token").notNull().unique(),
 created_at: timestamp("created_at").notNull(),
 updated_at: timestamp("updated_at").notNull(),
 ip_address: text("ip_address"),
 user_agent: text("user_agent"),
 user_id: text("user_id").notNull().references(() => users.id)
})

export const accounts = pgTable("accounts", {
 id: text("id").primaryKey(),
 account_id: text("account_id").notNull(),
 provider_id: text("provider_id").notNull(),
 user_id: text("user_id").notNull().references(() => users.id),
 access_token: text("access_token"),
 refresh_token: text("refresh_token"),
 id_token: text("id_token"),
 access_token_expires_at: timestamp("access_token_expires_at"),
 refresh_token_expires_at: timestamp("refresh_token_expires_at"),
 scope: text("scope"),
 password: text("password"),
 created_at: timestamp("created_at").notNull(),
 updated_at: timestamp("updated_at").notNull()
})

export const verifications = pgTable("verifications", {
 id: text("id").primaryKey(),
 identifier: text("identifier").notNull(),
 value: text("value").notNull(),
 expires_at: timestamp("expires_at").notNull(),
 created_at: timestamp("created_at"),
 updated_at: timestamp("updated_at")
})

export const jwks = pgTable("jwks", {
 id: text("id").primaryKey(),
 public_key: text("public_key").notNull(),
 private_key: text("private_key").notNull(),
 created_at: timestamp("created_at").notNull()
})
<!-- gh-comment-id:2552818069 --> @daveycodez commented on GitHub (Dec 19, 2024): Not gonna lie guys I used ChatGPT and I copied the entire documentation page of the Database info and this is the output for your easy use. This is my current implementation for snake_case with Drizzle last night. I'm on my phone rn I'll add formatting and code block later. ```ts import { betterAuth } from "better-auth" export const auth = betterAuth({ user: { modelName: "users", fields: { name: "name", email: "email", emailVerified: "email_verified", image: "image", createdAt: "created_at", updatedAt: "updated_at" } }, session: { modelName: "sessions", fields: { userId: "user_id", token: "token", expiresAt: "expires_at", ipAddress: "ip_address", userAgent: "user_agent", createdAt: "created_at", updatedAt: "updated_at" } }, account: { modelName: "accounts", fields: { userId: "user_id", accountId: "account_id", providerId: "provider_id", accessToken: "access_token", refreshToken: "refresh_token", accessTokenExpiresAt: "access_token_expires_at", refreshTokenExpiresAt: "refresh_token_expires_at", scope: "scope", password: "password", createdAt: "created_at", updatedAt: "updated_at" } }, verification: { modelName: "verifications", fields: { identifier: "identifier", value: "value", expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at" } } }) import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core" export const users = pgTable("users", { id: text("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), email_verified: boolean("email_verified").notNull(), image: text("image"), created_at: timestamp("created_at").notNull(), updated_at: timestamp("updated_at").notNull() }) export const sessions = pgTable("sessions", { id: text("id").primaryKey(), expires_at: timestamp("expires_at").notNull(), token: text("token").notNull().unique(), created_at: timestamp("created_at").notNull(), updated_at: timestamp("updated_at").notNull(), ip_address: text("ip_address"), user_agent: text("user_agent"), user_id: text("user_id").notNull().references(() => users.id) }) export const accounts = pgTable("accounts", { id: text("id").primaryKey(), account_id: text("account_id").notNull(), provider_id: text("provider_id").notNull(), user_id: text("user_id").notNull().references(() => users.id), access_token: text("access_token"), refresh_token: text("refresh_token"), id_token: text("id_token"), access_token_expires_at: timestamp("access_token_expires_at"), refresh_token_expires_at: timestamp("refresh_token_expires_at"), scope: text("scope"), password: text("password"), created_at: timestamp("created_at").notNull(), updated_at: timestamp("updated_at").notNull() }) export const verifications = pgTable("verifications", { id: text("id").primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expires_at: timestamp("expires_at").notNull(), created_at: timestamp("created_at"), updated_at: timestamp("updated_at") }) export const jwks = pgTable("jwks", { id: text("id").primaryKey(), public_key: text("public_key").notNull(), private_key: text("private_key").notNull(), created_at: timestamp("created_at").notNull() }) ```
Author
Owner

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

@daveycodez long phone call

<!-- gh-comment-id:3016002737 --> @rebasecase commented on GitHub (Jun 28, 2025): @daveycodez long phone call
Author
Owner

@joeldesante commented on GitHub (Feb 2, 2026):

@daveycodez still on the phone?!

<!-- gh-comment-id:3833067377 --> @joeldesante commented on GitHub (Feb 2, 2026): @daveycodez still on the phone?!
Author
Owner

@daveycodez commented on GitHub (Feb 2, 2026):

Lol tbh thought this was solved already in a patch

<!-- gh-comment-id:3833458891 --> @daveycodez commented on GitHub (Feb 2, 2026): Lol tbh thought this was solved already in a patch
Author
Owner

@github-actions[bot] commented on GitHub (Apr 1, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4166561427 --> @github-actions[bot] commented on GitHub (Apr 1, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25580