Drizzle Mysql better-auth installation #1145

Closed
opened 2026-03-13 08:24:54 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @etienne1698 on GitHub (Apr 30, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Better-auth installation with drizzle/mysql is not working out of the box

sql: '\n' +
    'ALTER TABLE `account` ADD CONSTRAINT `account_user_id_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;',
  sqlState: '42000',
  sqlMessage: "BLOB/TEXT column 'user_id' used in key specification without a key length"

Current vs. Expected behavior

Expected: use the CLI to generate schema, run migration and it work

Current: it is not the case, need manual modifications of the schema

What version of Better Auth are you using?

1.2.7

Provide environment information

- OS: Windows 11

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

Package

Auth config (if applicable)

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "mysql",
  }),
  emailAndPassword: {
    enabled: true,
  },
  trustedOrigins: [process.env.FRONTEND_URL!, process.env.URL!],
  advanced: {
    crossSubDomainCookies: {
      enabled: true,
    },
  },
});

Additional context

No response

Originally created by @etienne1698 on GitHub (Apr 30, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Better-auth installation with drizzle/mysql is not working out of the box ``` sql: '\n' + 'ALTER TABLE `account` ADD CONSTRAINT `account_user_id_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;', sqlState: '42000', sqlMessage: "BLOB/TEXT column 'user_id' used in key specification without a key length" ``` ### Current vs. Expected behavior Expected: use the CLI to generate schema, run migration and it work Current: it is not the case, need manual modifications of the schema ### What version of Better Auth are you using? 1.2.7 ### Provide environment information ```bash - OS: Windows 11 ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "mysql", }), emailAndPassword: { enabled: true, }, trustedOrigins: [process.env.FRONTEND_URL!, process.env.URL!], advanced: { crossSubDomainCookies: { enabled: true, }, }, }); ``` ### Additional context _No response_
Author
Owner

@pedrorivald commented on GitHub (May 3, 2025):

you need to inform the drizzle schema

https://orm.drizzle.team/docs/schemas
https://www.better-auth.com/docs/adapters/drizzle#schema-generation--migration

database: drizzleAdapter(db, {
    provider: "pg",
    schema: schema,
})
@pedrorivald commented on GitHub (May 3, 2025): you need to inform the drizzle schema https://orm.drizzle.team/docs/schemas https://www.better-auth.com/docs/adapters/drizzle#schema-generation--migration ```typescript database: drizzleAdapter(db, { provider: "pg", schema: schema, }) ```
Author
Owner

@Andresuito commented on GitHub (May 7, 2025):

The same thing happens to me

auth.ts

export const auth = betterAuth({
  appName: "Anticommon Dashboard",
  database: drizzleAdapter(db, {
    provider: "mysql",
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false,
    autoSignIn: true,
    minPasswordLength: 8,
    maxPasswordLength: 20,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    updateAge: 60 * 60 * 24,
  },
  plugins: [
    admin({
      defaultRole: "user",
    }),
    organization(),
    nextCookies(),
  ],
});

I run the command npx @better-auth/cli generate and it generates the schema perfectly.

import {
  mysqlTable,
  varchar,
  text,
  timestamp,
  boolean,
  int,
} from "drizzle-orm/mysql-core";

export const user = mysqlTable("user", {
  id: varchar("id", { length: 36 }).primaryKey(),
  name: text("name").notNull(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  emailVerified: boolean("email_verified").notNull(),
  image: text("image"),
  createdAt: timestamp("created_at").notNull(),
  updatedAt: timestamp("updated_at").notNull(),
  role: text("role"),
  banned: boolean("banned"),
  banReason: text("ban_reason"),
  banExpires: timestamp("ban_expires"),
});

export const session = mysqlTable("session", {
  id: varchar("id", { length: 36 }).primaryKey(),
  expiresAt: timestamp("expires_at").notNull(),
  token: varchar("token", { length: 255 }).notNull().unique(),
  createdAt: timestamp("created_at").notNull(),
  updatedAt: timestamp("updated_at").notNull(),
  ipAddress: text("ip_address"),
  userAgent: text("user_agent"),
  userId: text("user_id")
    .notNull()
    .references(() => user.id, { onDelete: "cascade" }),
  impersonatedBy: text("impersonated_by"),
  activeOrganizationId: text("active_organization_id"),
});

export const account = mysqlTable("account", {
  id: varchar("id", { length: 36 }).primaryKey(),
  accountId: text("account_id").notNull(),
  providerId: text("provider_id").notNull(),
  userId: text("user_id")
    .notNull()
    .references(() => user.id, { onDelete: "cascade" }),
  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("scope"),
  password: text("password"),
  createdAt: timestamp("created_at").notNull(),
  updatedAt: timestamp("updated_at").notNull(),
});

export const verification = mysqlTable("verification", {
  id: varchar("id", { length: 36 }).primaryKey(),
  identifier: text("identifier").notNull(),
  value: text("value").notNull(),
  expiresAt: timestamp("expires_at").notNull(),
  createdAt: timestamp("created_at"),
  updatedAt: timestamp("updated_at"),
});

export const organization = mysqlTable("organization", {
  id: varchar("id", { length: 36 }).primaryKey(),
  name: text("name").notNull(),
  slug: varchar("slug", { length: 255 }).unique(),
  logo: text("logo"),
  createdAt: timestamp("created_at").notNull(),
  metadata: text("metadata"),
});

export const member = mysqlTable("member", {
  id: varchar("id", { length: 36 }).primaryKey(),
  organizationId: text("organization_id")
    .notNull()
    .references(() => organization.id, { onDelete: "cascade" }),
  userId: text("user_id")
    .notNull()
    .references(() => user.id, { onDelete: "cascade" }),
  role: text("role").notNull(),
  createdAt: timestamp("created_at").notNull(),
});

export const invitation = mysqlTable("invitation", {
  id: varchar("id", { length: 36 }).primaryKey(),
  organizationId: text("organization_id")
    .notNull()
    .references(() => organization.id, { onDelete: "cascade" }),
  email: text("email").notNull(),
  role: text("role"),
  status: text("status").notNull(),
  expiresAt: timestamp("expires_at").notNull(),
  inviterId: text("inviter_id")
    .notNull()
    .references(() => user.id, { onDelete: "cascade" }),
});

but when I try to push to the database, it says

Error: BLOB/TEXT column 'user_id' used in key specification without a key length at PromiseConnection.execute (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/mysql2/lib/promise/connection.js:47:22) at Object.query (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:79120:40) at mysqlPush (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:82230:22) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.handler (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:92626:9) at async run (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:91903:7) { code: 'ER_BLOB_KEY_WITHOUT_LENGTH', errno: 1170, sql: 'ALTER TABLEaccountADD CONSTRAINTaccount_user_id_user_id_fk FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE cascade ON UPDATE no action;', sqlState: '42000', sqlMessage: "BLOB/TEXT column 'user_id' used in key specification without a key length" }

@Andresuito commented on GitHub (May 7, 2025): The same thing happens to me auth.ts ``` export const auth = betterAuth({ appName: "Anticommon Dashboard", database: drizzleAdapter(db, { provider: "mysql", }), emailAndPassword: { enabled: true, requireEmailVerification: false, autoSignIn: true, minPasswordLength: 8, maxPasswordLength: 20, }, session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, }, plugins: [ admin({ defaultRole: "user", }), organization(), nextCookies(), ], }); ``` I run the command **npx @better-auth/cli generate** and it generates the schema perfectly. ``` import { mysqlTable, varchar, text, timestamp, boolean, int, } from "drizzle-orm/mysql-core"; export const user = mysqlTable("user", { id: varchar("id", { length: 36 }).primaryKey(), name: text("name").notNull(), email: varchar("email", { length: 255 }).notNull().unique(), emailVerified: boolean("email_verified").notNull(), image: text("image"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), role: text("role"), banned: boolean("banned"), banReason: text("ban_reason"), banExpires: timestamp("ban_expires"), }); export const session = mysqlTable("session", { id: varchar("id", { length: 36 }).primaryKey(), expiresAt: timestamp("expires_at").notNull(), token: varchar("token", { length: 255 }).notNull().unique(), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), impersonatedBy: text("impersonated_by"), activeOrganizationId: text("active_organization_id"), }); export const account = mysqlTable("account", { id: varchar("id", { length: 36 }).primaryKey(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), 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("scope"), password: text("password"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), }); export const verification = mysqlTable("verification", { id: varchar("id", { length: 36 }).primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at"), updatedAt: timestamp("updated_at"), }); export const organization = mysqlTable("organization", { id: varchar("id", { length: 36 }).primaryKey(), name: text("name").notNull(), slug: varchar("slug", { length: 255 }).unique(), logo: text("logo"), createdAt: timestamp("created_at").notNull(), metadata: text("metadata"), }); export const member = mysqlTable("member", { id: varchar("id", { length: 36 }).primaryKey(), organizationId: text("organization_id") .notNull() .references(() => organization.id, { onDelete: "cascade" }), userId: text("user_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), role: text("role").notNull(), createdAt: timestamp("created_at").notNull(), }); export const invitation = mysqlTable("invitation", { id: varchar("id", { length: 36 }).primaryKey(), organizationId: text("organization_id") .notNull() .references(() => organization.id, { onDelete: "cascade" }), email: text("email").notNull(), role: text("role"), status: text("status").notNull(), expiresAt: timestamp("expires_at").notNull(), inviterId: text("inviter_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), }); ``` but when I try to push to the database, it says ` Error: BLOB/TEXT column 'user_id' used in key specification without a key length at PromiseConnection.execute (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/mysql2/lib/promise/connection.js:47:22) at Object.query (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:79120:40) at mysqlPush (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:82230:22) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.handler (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:92626:9) at async run (/Users/andresbr/Proyectos/anticommon-dashboard/node_modules/drizzle-kit/bin.cjs:91903:7) { code: 'ER_BLOB_KEY_WITHOUT_LENGTH', errno: 1170, sql: 'ALTER TABLE `account` ADD CONSTRAINT `account_user_id_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;', sqlState: '42000', sqlMessage: "BLOB/TEXT column 'user_id' used in key specification without a key length" }`
Author
Owner

@obendev commented on GitHub (May 9, 2025):

Reproduced on fresh install with drizzle + MySQL 8.4
This is basically what was once discussed in /pull/672
@gltjk I agree with what you said there.

If you use a TEXT (or BLOB) column as part of a key (like a foreign key or an index), you must specify a prefix length for that key. This is because TEXT columns don't have a predefined maximum length.
Many columns that are intended to be foreign keys referencing user.id (or other varchar(36) primary keys) are defined as text.
When Drizzle tries to create a foreign key constraint like FOREIGN KEY (user_id) REFERENCES user(id), MySQL sees thatuser_id(in theaccountorsessiontable) is aTEXTcolumn and throws the error because no key length is specified for thisTEXT column in the key.
So, any column like userId that references user.id (which is varchar(36)) should also be varchar('user_id', { length: 36 }

@obendev commented on GitHub (May 9, 2025): Reproduced on fresh install with drizzle + MySQL 8.4 This is basically what was once discussed in /pull/672 @gltjk I agree with what you said there. If you use a `TEXT` (or `BLOB`) column as part of a key (like a foreign key or an index), you _must_ specify a prefix length for that key. This is because `TEXT` columns don't have a predefined maximum length. Many columns that are intended to be foreign keys referencing `user.id` (or other `varchar(36)` primary keys) are defined as `text`. When Drizzle tries to create a foreign key constraint like `FOREIGN KEY (user_id) REFERENCES user(id)`, MySQL sees that`user_id`(in the`account`or`session`table) is a`TEXT`column and throws the error because no key length is specified for this`TEXT` column in the key. So, any column like `userId` that references `user.id` (which is `varchar(36)`) should also be `varchar('user_id', { length: 36 }`
Author
Owner

@dosubot[bot] commented on GitHub (Aug 8, 2025):

Hi, @etienne1698. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that Better Auth v1.2.7 with Drizzle MySQL on Windows 11 fails due to MySQL errors about BLOB/TEXT columns in foreign keys lacking key lengths.
  • The problem was that foreign key columns like user_id were defined as TEXT instead of varchar(36), causing MySQL to reject the schema.
  • Other users confirmed the issue and shared similar experiences.
  • The root cause was schema generation not aligning column types properly for MySQL foreign keys.
  • The resolution involved changing foreign key columns from TEXT to varchar(36), enabling automatic migrations to work correctly.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of better-auth.
  • If it is, feel free to comment to keep the discussion open; otherwise, I will close this issue automatically in 7 days.

Thanks for your understanding and contribution!

@dosubot[bot] commented on GitHub (Aug 8, 2025): Hi, @etienne1698. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that Better Auth v1.2.7 with Drizzle MySQL on Windows 11 fails due to MySQL errors about BLOB/TEXT columns in foreign keys lacking key lengths. - The problem was that foreign key columns like user_id were defined as TEXT instead of varchar(36), causing MySQL to reject the schema. - Other users confirmed the issue and shared similar experiences. - The root cause was schema generation not aligning column types properly for MySQL foreign keys. - The resolution involved changing foreign key columns from TEXT to varchar(36), enabling automatic migrations to work correctly. **Next Steps:** - Please confirm if this issue is still relevant with the latest version of better-auth. - If it is, feel free to comment to keep the discussion open; otherwise, I will close this issue automatically in 7 days. Thanks for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1145