snake case doesn't work for postgres.js with kysely adapter #1983

Closed
opened 2026-03-13 09:18:58 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @AceCodePt on GitHub (Sep 20, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create an astro project
  2. Create the sql.ts file:
import postgres from "postgres";

const sql = postgres({
  transform: {
    undefined: null,
    ...postgres.camel,
  }
});

  1. Place the following auth.ts in src.
  2. Run migraion using npm/pnpm

Current vs. Expected behavior

create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "email_verified" boolean not null, "image" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null);

create table "session" ("id" text not null primary key, "expires_at" timestamptz not null, "token" text not null unique, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null, "ip_address" text, "user_agent" text, "user_id" text not null references "user" ("id") on delete cascade);

create table "account" ("id" text not null primary key, "account_id" text not null, "provider_id" text not null, "user_id" text not null references "user" ("id") on delete cascade, "access_token" text, "refresh_token" text, "id_token" text, "access_token_expires_at" timestamptz, "refresh_token_expires_at" timestamptz, "scope" text, "password" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null);

create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expires_at" timestamptz not null, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null);

What version of Better Auth are you using?

1.3.12

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#78-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 11:34:18 UTC 2025",
    "release": "6.8.0-78-generic",
    "cpuCount": 20,
    "cpuModel": "12th Gen Intel(R) Core(TM) i7-12700H",
    "totalMemory": "62.50 GB",
    "freeMemory": "51.48 GB"
  },
  "node": {
    "version": "v24.6.0",
    "env": "development"
  },
  "packageManager": {
    "name": "pnpm",
    "version": "10.17.0"
  },
  "frameworks": [
    {
      "name": "astro",
      "version": "^5.13.7"
    }
  ],
  "databases": [
    {
      "name": "postgres",
      "version": "^3.4.7"
    }
  ],
  "betterAuth": {
    "version": "^1.3.12",
    "config": {
      "database": {
        "dialect": {},
        "schema": "auth",
        "type": "postgres",
        "casing": "snake"
      }
    }
  }
}

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

Other, Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import sql from "./sql";
import { PostgresJSDialect } from "kysely-postgres-js";

export const auth = betterAuth({
  database: {
    dialect: new PostgresJSDialect({
      postgres: sql,
    }),
    schema: "auth",
    type: "postgres",
    casing: "snake",
  },
});

Additional context

Make sure to install:
"kysely-postgres-js": "^3.0.0",
"postgres": "^3.4.7",

Originally created by @AceCodePt on GitHub (Sep 20, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create an astro project 2. Create the sql.ts file: ```ts import postgres from "postgres"; const sql = postgres({ transform: { undefined: null, ...postgres.camel, } }); ``` 3. Place the following auth.ts in src. 4. Run migraion using npm/pnpm ### Current vs. Expected behavior ```sql create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "email_verified" boolean not null, "image" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null); create table "session" ("id" text not null primary key, "expires_at" timestamptz not null, "token" text not null unique, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null, "ip_address" text, "user_agent" text, "user_id" text not null references "user" ("id") on delete cascade); create table "account" ("id" text not null primary key, "account_id" text not null, "provider_id" text not null, "user_id" text not null references "user" ("id") on delete cascade, "access_token" text, "refresh_token" text, "id_token" text, "access_token_expires_at" timestamptz, "refresh_token_expires_at" timestamptz, "scope" text, "password" text, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz not null); create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expires_at" timestamptz not null, "created_at" timestamptz default CURRENT_TIMESTAMP not null, "updated_at" timestamptz default CURRENT_TIMESTAMP not null); ``` ### What version of Better Auth are you using? 1.3.12 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#78-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 11:34:18 UTC 2025", "release": "6.8.0-78-generic", "cpuCount": 20, "cpuModel": "12th Gen Intel(R) Core(TM) i7-12700H", "totalMemory": "62.50 GB", "freeMemory": "51.48 GB" }, "node": { "version": "v24.6.0", "env": "development" }, "packageManager": { "name": "pnpm", "version": "10.17.0" }, "frameworks": [ { "name": "astro", "version": "^5.13.7" } ], "databases": [ { "name": "postgres", "version": "^3.4.7" } ], "betterAuth": { "version": "^1.3.12", "config": { "database": { "dialect": {}, "schema": "auth", "type": "postgres", "casing": "snake" } } } } ``` ### Which area(s) are affected? (Select all that apply) Other, Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import sql from "./sql"; import { PostgresJSDialect } from "kysely-postgres-js"; export const auth = betterAuth({ database: { dialect: new PostgresJSDialect({ postgres: sql, }), schema: "auth", type: "postgres", casing: "snake", }, }); ``` ### Additional context Make sure to install: "kysely-postgres-js": "^3.0.0", "postgres": "^3.4.7",
GiteaMirror added the astrobug labels 2026-03-13 09:18:58 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 20, 2025):

This is a known limitation: the casing: "snake" option in Better Auth does not automatically apply to the Kysely adapter with postgres.js, so queries still use camelCase column names, which leads to errors like column "emailVerified" does not exist if your DB uses snake_case columns. The recommended workaround is to manually map each logical (camelCase) field to its snake_case DB column in the fields property for every model in your Better Auth config (user, account, session, etc.) — for example, emailVerified: "email_verified", createdAt: "created_at", and so on (see example and discussion).

If you use Kysely's CamelCasePlugin or set up postgres.js with its own transform (like postgres.camel), you may get mismatches between the keys returned by Kysely (camelCase) and what Better Auth expects when custom field mappings are used. To avoid this, either remove custom field mappings and rely on the plugin (if your logical and DB field names match after camelCasing), or use Better Auth's customTransformOutput to remap keys from camelCase (as returned by Kysely) to the logical field names expected by Better Auth (details and workaround).

Also, the migrate command in Better Auth uses the built-in Kysely adapter, which currently generates camelCase columns by default and does not support snake_case in migrations (tracked here). For now, manual mapping is the most reliable solution.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 20, 2025): <!-- Answer --> This is a known limitation: the `casing: "snake"` option in Better Auth does not automatically apply to the Kysely adapter with postgres.js, so queries still use camelCase column names, which leads to errors like `column "emailVerified" does not exist` if your DB uses snake_case columns. The recommended workaround is to manually map each logical (camelCase) field to its snake_case DB column in the `fields` property for every model in your Better Auth config (user, account, session, etc.) — for example, `emailVerified: "email_verified"`, `createdAt: "created_at"`, and so on ([see example and discussion](https://github.com/better-auth/better-auth/issues/1027)). If you use Kysely's `CamelCasePlugin` or set up `postgres.js` with its own transform (like `postgres.camel`), you may get mismatches between the keys returned by Kysely (camelCase) and what Better Auth expects when custom field mappings are used. To avoid this, either remove custom field mappings and rely on the plugin (if your logical and DB field names match after camelCasing), or use Better Auth's `customTransformOutput` to remap keys from camelCase (as returned by Kysely) to the logical field names expected by Better Auth ([details and workaround](https://github.com/better-auth/better-auth/issues/3212)). Also, the `migrate` command in Better Auth uses the built-in Kysely adapter, which currently generates camelCase columns by default and does not support snake_case in migrations ([tracked here](https://github.com/better-auth/better-auth/issues/799)). For now, manual mapping is the most reliable solution. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/84c25502-d6a4-4a8d-93a9-287868001eae?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4789)
Author
Owner

@AceCodePt commented on GitHub (Sep 21, 2025):

My solution for it was as it suggested:

export const auth = betterAuth({
  database: {
    dialect: new PostgresJSDialect({
      postgres: sql,
    }),
    schema: "auth",
    type: "postgres",
    casing: "snake",
  },
  user: {
    fields: {
      emailVerified: "email_verified",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },
  },
  session: {
    fields: {
      userId: "user_id",
      expiresAt: "expires_at",
      createdAt: "created_at",
      updatedAt: "updated_at",
      ipAddress: "ip_address",
      userAgent: "user_agent",
    },
  },
  account: {
    fields: {
      userId: "user_id",
      accountId: "account_id",
      providerId: "provider_id",
      accessToken: "access_token",
      refreshToken: "refresh_token",
      idToken: "id_token",
      accessTokenExpiresAt: "access_token_expires_at",
      refreshTokenExpiresAt: "refresh_token_expires_at",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },
  },
  verification: {
    fields: {
      expiresAt: "expires_at",
      createdAt: "created_at",
      updatedAt: "updated_at",
    },
  },
});
@AceCodePt commented on GitHub (Sep 21, 2025): My solution for it was as it suggested: ```ts export const auth = betterAuth({ database: { dialect: new PostgresJSDialect({ postgres: sql, }), schema: "auth", type: "postgres", casing: "snake", }, user: { fields: { emailVerified: "email_verified", createdAt: "created_at", updatedAt: "updated_at", }, }, session: { fields: { userId: "user_id", expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at", ipAddress: "ip_address", userAgent: "user_agent", }, }, account: { fields: { userId: "user_id", accountId: "account_id", providerId: "provider_id", accessToken: "access_token", refreshToken: "refresh_token", idToken: "id_token", accessTokenExpiresAt: "access_token_expires_at", refreshTokenExpiresAt: "refresh_token_expires_at", createdAt: "created_at", updatedAt: "updated_at", }, }, verification: { fields: { expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at", }, }, }); ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1983