Generated drizzle schemas ignore existing database structure #2281

Closed
opened 2026-03-13 09:40:51 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @brachkow on GitHub (Nov 9, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Start project using Drizzle ORM and SQLite. Have at least one database table which name collides with one of the tables required by better-auth. Add an extra column with any name that is not required by better-auth.
  2. Run pnpm dlx @better-auth/cli generate, and see resulting schema

Current vs. Expected behavior

Current behavior:
New schema doesn't have extra columns, which will result in them being deleted on push

Expected behavior:
Resulting schema must retain columns that existed before + add/update columns needed by better-auth.

I observed such behavior while using @better-auth/cli generate with Kysely

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000",
    "release": "25.1.0",
    "cpuCount": 8,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.12 GB"
  },
  "node": {
    "version": "v22.17.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.41.0"
    },
    {
      "name": "@sveltejs/kit",
      "version": "^2.47.1"
    },
    {
      "name": "hono",
      "version": "^4.10.2"
    }
  ],
  "databases": [
    {
      "name": "better-sqlite3",
      "version": "^12.4.1"
    },
    {
      "name": "drizzle",
      "version": "^0.44.7"
    }
  ],
  "betterAuth": {
    "version": "^1.3.34",
    "config": {
      "user": {
        "fields": {
          "name": "username"
        }
      },
      "plugins": [
        {
          "name": "email-otp",
          "config": {
            "id": "email-otp",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "$ERROR_CODES": {
              "OTP_EXPIRED": "otp expired",
              "INVALID_OTP": "Invalid OTP",
              "INVALID_EMAIL": "Invalid email",
              "USER_NOT_FOUND": "User not found",
              "TOO_MANY_ATTEMPTS": "Too many attempts"
            },
            "rateLimit": [
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              }
            ]
          }
        }
      ]
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { emailOTP } from 'better-auth/plugins';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import Database from 'better-sqlite3';
import { getLocalD1DB } from './drizzle.config.local';
import * as schema from './src/lib/server/schemas/database';
import * as relations from './src/lib/server/schemas/relations';

export const auth = betterAuth({
  database: drizzleAdapter(new Database(getLocalD1DB()), {
    provider: 'sqlite',
    schema: {
      ...schema,
      ...relations,
    },
    usePlural: true,
  }),
  user: {
    fields: {
      name: 'username',
    },
  },
  plugins: [
    emailOTP({
      sendVerificationOTP: async ({ email, otp, type }) => {
        console.log(
          `Sending email to ${email} with code ${otp} for type ${type}`,
        );
      },
    }),
  ],
});

Additional context

For example — here onlineAt and isOnboarded will dissapear:

Before:

export const users = sqliteTable(
  'users',
  {
    id: text().primaryKey().notNull(),
    createdAt: text().default('sql`(CURRENT_TIMESTAMP)`').notNull(),
    updatedAt: text().default('sql`(CURRENT_TIMESTAMP)`').notNull(),
    email: text().notNull(),
    username: text().notNull(),
    onlineAt: text().default('sql`(NULL)`'),
    isOnboarded: integer({ mode: 'boolean' }).default(false).notNull(),
  },
  (table) => [
    uniqueIndex('uq_users_username').on(table.username),
    uniqueIndex('uq_users_email').on(table.email),
    index('idx_users_username').on(table.username),
    index('idx_users_email').on(table.email),
    index('idx_users_id').on(table.id),
  ],
);

Generated:

export const users = sqliteTable("users", {
  id: text("id").primaryKey(),
  username: text("username").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(() => /* @__PURE__ */ new Date())
    .notNull(),
});
Originally created by @brachkow on GitHub (Nov 9, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Start project using Drizzle ORM and SQLite. Have at least one database table which name collides with one of the tables required by better-auth. Add an extra column with any name that is not required by better-auth. 2. Run `pnpm dlx @better-auth/cli generate`, and see resulting schema ### Current vs. Expected behavior Current behavior: New schema doesn't have extra columns, which will result in them being deleted on push Expected behavior: Resulting schema must retain columns that existed before + add/update columns needed by better-auth. I observed such behavior while using `@better-auth/cli `generate with Kysely ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000", "release": "25.1.0", "cpuCount": 8, "cpuModel": "Apple M1 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.12 GB" }, "node": { "version": "v22.17.1", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "svelte", "version": "^5.41.0" }, { "name": "@sveltejs/kit", "version": "^2.47.1" }, { "name": "hono", "version": "^4.10.2" } ], "databases": [ { "name": "better-sqlite3", "version": "^12.4.1" }, { "name": "drizzle", "version": "^0.44.7" } ], "betterAuth": { "version": "^1.3.34", "config": { "user": { "fields": { "name": "username" } }, "plugins": [ { "name": "email-otp", "config": { "id": "email-otp", "endpoints": {}, "hooks": { "after": [ {} ] }, "$ERROR_CODES": { "OTP_EXPIRED": "otp expired", "INVALID_OTP": "Invalid OTP", "INVALID_EMAIL": "Invalid email", "USER_NOT_FOUND": "User not found", "TOO_MANY_ATTEMPTS": "Too many attempts" }, "rateLimit": [ { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 } ] } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { emailOTP } from 'better-auth/plugins'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import Database from 'better-sqlite3'; import { getLocalD1DB } from './drizzle.config.local'; import * as schema from './src/lib/server/schemas/database'; import * as relations from './src/lib/server/schemas/relations'; export const auth = betterAuth({ database: drizzleAdapter(new Database(getLocalD1DB()), { provider: 'sqlite', schema: { ...schema, ...relations, }, usePlural: true, }), user: { fields: { name: 'username', }, }, plugins: [ emailOTP({ sendVerificationOTP: async ({ email, otp, type }) => { console.log( `Sending email to ${email} with code ${otp} for type ${type}`, ); }, }), ], }); ``` ### Additional context For example — here `onlineAt` and `isOnboarded` will dissapear: Before: ``` export const users = sqliteTable( 'users', { id: text().primaryKey().notNull(), createdAt: text().default('sql`(CURRENT_TIMESTAMP)`').notNull(), updatedAt: text().default('sql`(CURRENT_TIMESTAMP)`').notNull(), email: text().notNull(), username: text().notNull(), onlineAt: text().default('sql`(NULL)`'), isOnboarded: integer({ mode: 'boolean' }).default(false).notNull(), }, (table) => [ uniqueIndex('uq_users_username').on(table.username), uniqueIndex('uq_users_email').on(table.email), index('idx_users_username').on(table.username), index('idx_users_email').on(table.email), index('idx_users_id').on(table.id), ], ); ``` Generated: ``` export const users = sqliteTable("users", { id: text("id").primaryKey(), username: text("username").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(() => /* @__PURE__ */ new Date()) .notNull(), }); ```
GiteaMirror added the bug label 2026-03-13 09:40:52 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 9, 2025):

No reply.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 9, 2025): No reply. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 9, 2025):

This is a known limitation: the Better Auth CLI for Drizzle ORM does not merge or preserve extra columns from your existing database tables when generating schemas. It overwrites the schema file with only the fields required by Better Auth, so any custom columns (like onlineAt or isOnboarded) will be lost unless you manually re-add them after generation. There is currently no configuration or CLI option to change this behavior, and similar issues have been reported for other adapters like Kysely as well. The recommended workaround is to manually update the generated schema files to include your custom columns before running migrations or pushing changes [source].

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 (Nov 9, 2025): <!-- Answer --> This is a known limitation: the Better Auth CLI for Drizzle ORM does not merge or preserve extra columns from your existing database tables when generating schemas. It overwrites the schema file with only the fields required by Better Auth, so any custom columns (like `onlineAt` or `isOnboarded`) will be lost unless you manually re-add them after generation. There is currently no configuration or CLI option to change this behavior, and similar issues have been reported for other adapters like Kysely as well. The recommended workaround is to manually update the generated schema files to include your custom columns before running migrations or pushing changes [[source](https://github.com/better-auth/better-auth/blob/7067b58d358af0fb28ce4d272513ef21186c253c/packages/cli/src/generators/drizzle.ts#L11-L221)]. <!-- 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/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/de20b810-1d70-4173-9886-416fe2156ad5?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/5874)
Author
Owner

@brachkow commented on GitHub (Nov 9, 2025):

I saw that I forgot to use usePlural, but adding it didn't help. Updated examples.

@brachkow commented on GitHub (Nov 9, 2025): I saw that I forgot to use usePlural, but adding it didn't help. Updated examples.
Author
Owner

@dosubot[bot] commented on GitHub (Nov 9, 2025):

The usePlural option only changes whether the generated table names are pluralized (like users) or singular (like user). It doesn't affect which columns are included in the generated schema or help preserve any extra columns you had in your original tables. The CLI generator always overwrites the schema with just the fields required by Better Auth, so any custom columns will still be lost regardless of usePlural's setting. To keep your extra columns, you'll need to manually add them back to the generated schema after running the CLI generate command. There isn't currently a built-in way to merge or preserve custom columns automatically.
Reference: generator code

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 (Nov 9, 2025): <!-- Answer --> The usePlural option only changes whether the generated table names are pluralized (like users) or singular (like user). It doesn't affect which columns are included in the generated schema or help preserve any extra columns you had in your original tables. The CLI generator always overwrites the schema with just the fields required by Better Auth, so any custom columns will still be lost regardless of usePlural's setting. To keep your extra columns, you'll need to manually add them back to the generated schema after running the CLI generate command. There isn't currently a built-in way to merge or preserve custom columns automatically. [Reference: generator code](https://github.com/better-auth/better-auth/blob/7067b58d358af0fb28ce4d272513ef21186c253c/packages/cli/src/generators/drizzle.ts#L22-L333) <!-- 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/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/28d13fa7-dcb6-469c-832f-971ab80b5eb7?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/5874)
Author
Owner

@brachkow commented on GitHub (Nov 9, 2025):

Not sure that what is ai is saying is true, because im sure that I observed desired behavior with kysely.

@brachkow commented on GitHub (Nov 9, 2025): Not sure that what is ai is saying is true, because im sure that I observed desired behavior with kysely.
Author
Owner

@brachkow commented on GitHub (Nov 9, 2025):

Btw, it's also very strange that generated columns are in snake_case, while in better-auth docs they are in camelCase.

UPD: Ok, it actually is fixed via adding camelCase option to drizzleAdatpter

@brachkow commented on GitHub (Nov 9, 2025): Btw, it's also very strange that generated columns are in snake_case, while in better-auth docs they are in camelCase. UPD: Ok, it actually is fixed via adding camelCase option to drizzleAdatpter
Author
Owner

@himself65 commented on GitHub (Dec 2, 2025):

Fixed in the latest version

@himself65 commented on GitHub (Dec 2, 2025): Fixed in the latest version
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2281