Date Handling Issues with Better Auth and PostgreSQL Integration (Supabase) #660

Closed
opened 2026-03-13 07:59:25 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @RattyJhay on GitHub (Feb 11, 2025).

When using Better Auth with Supabase and the drizzle adapter, there are several date handling issues that cause TypeErrors during both user creation and authentication processes.

ERROR [Better Auth]: Failed to create user TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date

Environment

Better Auth version: 1.1.16
Database: PostgreSQL (Supabase)
ORM: Drizzle
Node.js version: ^22.5.5
TypeScript version: ^5.6.2

*Issues
1. Date Type Error During Sign Up
When creating a new user, we encounter:
TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date

2. Date Type Error During Sign In
When attempting to sign in, we get:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import db from "../config/db.js";

const formatLog = (message: string, ...args: any[]) => {
  const sanitizedArgs = args.map((arg) => {
    if (!arg) return arg;
    if (typeof arg === "object") {
      const sanitized = { ...arg };
      if (sanitized.password) sanitized.password = "[HIDDEN]";
      if (sanitized.token) sanitized.token = "[HIDDEN]";
      if (sanitized.accessToken) sanitized.accessToken = "[HIDDEN]";
      if (sanitized.refreshToken) sanitized.refreshToken = "[HIDDEN]";
      return sanitized;
    }
    return arg;
  });

  return [message, ...sanitizedArgs];
};

export const auth = betterAuth({
  baseUrl: "/api/auth",
  database: drizzleAdapter(db, {
    provider: "pg",
    usePlural: true,
  }),
  autoSignIn: false,
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 6,
    requireEmailVerification: false,
  },
  session: {
    updateAge: 7,
  },
  user: {
    fields: {
      name: "name",
      email: "email",
    },
    additionalFields: {
      firstName: {
        type: "string",
        required: false,
        input: true,
      },
      lastName: {
        type: "string",
        required: false,
        input: true,
      },
      phoneNumber: {
        type: "string",
        required: false,
        input: true,
      },
      roleId: {
        type: "number",
        required: false,
        defaultValue: 1,
        input: false,
      },
      companyId: {
        type: "string",
        required: false,
        input: false,
      },
    },
  },
  advanced: {
    generateId: false,
  },
  databaseHooks: {
    account: {
      create: {
        before: async (account) => {
          const formattedAccount = {
            ...account,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
          };

          console.log("Creating account:", {
            ...formattedAccount,
            password: "[HIDDEN]",
          });

          return {
            data: formattedAccount,
          };
        },
        after: async (createdAccount) => {
          console.log("Account created:", {
            ...createdAccount,
            password: "[HIDDEN]",
          });
        },
      },
    },
    user: {
      create: {
        before: async (user) => {
          const [firstName, lastName] = (user.name || "").split(" ");

          const formattedUser = {
            id: user.id,
            name: user.name,
            email: user.email,
            emailVerified: true,
            firstName,
            lastName,
            roleId: 1,
            companyId: null,
            phoneNumber: user.image,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
          };

          console.log("Creating user:", formattedUser);

          return {
            data: formattedUser,
          };
        },
        after: async (createdUser) => {
          console.log("User created:", createdUser);
        },
      },
    },
  },
});

Database Schema
Our PostgreSQL schema uses timestamptz for date fields:

ALTER TABLE users 
  ALTER COLUMN created_at TYPE timestamptz,
  ALTER COLUMN updated_at TYPE timestamptz;

ALTER TABLE accounts
  ALTER COLUMN created_at TYPE timestamptz,
  ALTER COLUMN updated_at TYPE timestamptz;

Attempted Solutions
Using toISOString() for dates:

createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),

Using PostgreSQL timestamp format:
const now = new Date().toISOString().replace("T", " ").replace("Z", "+00");

Letting Better Auth handle dates without modification:
createdAt: new Date(), updatedAt: new Date(),

the authentication flow has a few issues when integrating supabase. my database and authentication system were already in place using passport, and i'm replacing passport with better auth. however, when creating a user, the account is successfully stored in the database, but the api returns a 500 error due to a date type mismatch. this makes it seem like the operation both succeeds and fails, creating a confusing experience. additionally, users can't log in after account creation, with an error pointing to invalid date type handling. the error message reads: typeerror [err_invalid_arg_type]: the "string" argument must be of type string or an instance of buffer or arraybuffer. received an instance of date.

Originally created by @RattyJhay on GitHub (Feb 11, 2025). When using Better Auth with Supabase and the drizzle adapter, there are several date handling issues that cause TypeErrors during both user creation and authentication processes. `ERROR [Better Auth]: Failed to create user TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date` **Environment** Better Auth version: 1.1.16 Database: PostgreSQL (Supabase) ORM: Drizzle Node.js version: ^22.5.5 TypeScript version: ^5.6.2 **Issues* **1. Date Type Error During Sign Up** When creating a new user, we encounter: `TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date` **2. Date Type Error During Sign In** When attempting to sign in, we get: `TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined` ``` import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import db from "../config/db.js"; const formatLog = (message: string, ...args: any[]) => { const sanitizedArgs = args.map((arg) => { if (!arg) return arg; if (typeof arg === "object") { const sanitized = { ...arg }; if (sanitized.password) sanitized.password = "[HIDDEN]"; if (sanitized.token) sanitized.token = "[HIDDEN]"; if (sanitized.accessToken) sanitized.accessToken = "[HIDDEN]"; if (sanitized.refreshToken) sanitized.refreshToken = "[HIDDEN]"; return sanitized; } return arg; }); return [message, ...sanitizedArgs]; }; export const auth = betterAuth({ baseUrl: "/api/auth", database: drizzleAdapter(db, { provider: "pg", usePlural: true, }), autoSignIn: false, emailAndPassword: { enabled: true, minPasswordLength: 6, requireEmailVerification: false, }, session: { updateAge: 7, }, user: { fields: { name: "name", email: "email", }, additionalFields: { firstName: { type: "string", required: false, input: true, }, lastName: { type: "string", required: false, input: true, }, phoneNumber: { type: "string", required: false, input: true, }, roleId: { type: "number", required: false, defaultValue: 1, input: false, }, companyId: { type: "string", required: false, input: false, }, }, }, advanced: { generateId: false, }, databaseHooks: { account: { create: { before: async (account) => { const formattedAccount = { ...account, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; console.log("Creating account:", { ...formattedAccount, password: "[HIDDEN]", }); return { data: formattedAccount, }; }, after: async (createdAccount) => { console.log("Account created:", { ...createdAccount, password: "[HIDDEN]", }); }, }, }, user: { create: { before: async (user) => { const [firstName, lastName] = (user.name || "").split(" "); const formattedUser = { id: user.id, name: user.name, email: user.email, emailVerified: true, firstName, lastName, roleId: 1, companyId: null, phoneNumber: user.image, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; console.log("Creating user:", formattedUser); return { data: formattedUser, }; }, after: async (createdUser) => { console.log("User created:", createdUser); }, }, }, }, }); ``` **Database Schema** Our PostgreSQL schema uses timestamptz for date fields: ``` ALTER TABLE users ALTER COLUMN created_at TYPE timestamptz, ALTER COLUMN updated_at TYPE timestamptz; ALTER TABLE accounts ALTER COLUMN created_at TYPE timestamptz, ALTER COLUMN updated_at TYPE timestamptz; ``` **Attempted Solutions** Using toISOString() for dates: `createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),` **Using PostgreSQL timestamp format:** `const now = new Date().toISOString().replace("T", " ").replace("Z", "+00");` **Letting Better Auth handle dates without modification:** `createdAt: new Date(), updatedAt: new Date(),` the authentication flow has a few issues when integrating supabase. my database and authentication system were already in place using passport, and i'm replacing passport with better auth. however, when creating a user, the account is successfully stored in the database, but the api returns a 500 error due to a date type mismatch. this makes it seem like the operation both succeeds and fails, creating a confusing experience. additionally, users can't log in after account creation, with an error pointing to invalid date type handling. the error message reads: typeerror [err_invalid_arg_type]: the "string" argument must be of type string or an instance of buffer or arraybuffer. received an instance of date.
Author
Owner

@Bekacru commented on GitHub (Feb 12, 2025):

can you share your drizzle auth schema?

@Bekacru commented on GitHub (Feb 12, 2025): can you share your drizzle auth schema?
Author
Owner

@RattyJhay commented on GitHub (Feb 12, 2025):

@Bekacru

auth-schema.ts

import {
  pgTable,
  text,
  integer,
  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(),
  emailVerified: boolean("email_verified").notNull(),
  image: text("image"),
  createdAt: timestamp("created_at").notNull(),
  updatedAt: timestamp("updated_at").notNull(),
  firstName: text("first_name"),
  lastName: text("last_name"),
  phoneNumber: text("phone_number"),
  roleId: integer("role_id"),
  companyId: text("company_id"),
});

export const sessions = pgTable("sessions", {
  id: text("id").primaryKey(),
  expiresAt: timestamp("expires_at").notNull(),
  token: text("token").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(() => users.id),
});

export const accounts = pgTable("accounts", {
  id: text("id").primaryKey(),
  accountId: text("account_id").notNull(),
  providerId: text("provider_id").notNull(),
  userId: text("user_id")
    .notNull()
    .references(() => users.id),
  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 verifications = pgTable("verifications", {
  id: text("id").primaryKey(),
  identifier: text("identifier").notNull(),
  value: text("value").notNull(),
  expiresAt: timestamp("expires_at").notNull(),
  createdAt: timestamp("created_at"),
  updatedAt: timestamp("updated_at"),
});

my drizzle schema pulled from supabase:

export const users = pgTable(
  "users",
  {
    id: uuid("id").defaultRandom().primaryKey().notNull(),
    firstName: varchar("first_name").notNull(),
    lastName: varchar("last_name").notNull(),
    email: varchar("email").notNull(),
    password: text("password"),
    roleId: integer("role_id").notNull(),
    companyId: uuid("company_id").defaultRandom(),
    createdAt: timestamp("created_at", {
      withTimezone: true,
      mode: "string",
    }).defaultNow(),
    updatedAt: timestamp("updated_at", {
      withTimezone: true,
      mode: "string",
    }).defaultNow(),
    phoneNumber: varchar("phone_number").notNull(),
    name: text("name"),
    emailVerified: boolean("email_verified").default(false),
    image: text("image"),
  },
  (table) => {
    return {
      usersCompanyIdFkey: foreignKey({
        columns: [table.companyId],
        foreignColumns: [companies.id],
        name: "users_company_id_fkey",
      })
        .onUpdate("cascade")
        .onDelete("set null"),
      usersCompanyIdFkey1: foreignKey({
        columns: [table.companyId],
        foreignColumns: [companies.id],
        name: "users_company_id_fkey1",
      })
        .onUpdate("cascade")
        .onDelete("set null"),
      usersEmailKey: unique("users_email_key").on(table.email),
    };
  },
) as ReturnType<typeof pgTable>;

export const syncMetadata = pgTable(
  "sync_metadata",
  {
    id: uuid("id").defaultRandom().primaryKey().notNull(),
    branchId: uuid("branch_id"),
    companyId: uuid("company_id"),
    lastSyncTimestamp: timestamp("last_sync_timestamp", {
      withTimezone: true,
      mode: "string",
    }).defaultNow(),
    syncStatus: text("sync_status").default("active"),
    clientIdentifier: text("client_identifier"),
    createdAt: timestamp("created_at", {
      withTimezone: true,
      mode: "string",
    }).defaultNow(),
    updatedAt: timestamp("updated_at", {
      withTimezone: true,
      mode: "string",
    }).defaultNow(),
  },
  (table) => {
    return {
      idxSyncMetadataBranch: index("idx_sync_metadata_branch").using(
        "btree",
        table.branchId.asc().nullsLast(),
      ),
      idxSyncMetadataCompany: index("idx_sync_metadata_company").using(
        "btree",
        table.companyId.asc().nullsLast(),
      ),
      syncMetadataBranchIdFkey: foreignKey({
        columns: [table.branchId],
        foreignColumns: [branches.id],
        name: "sync_metadata_branch_id_fkey",
      }),
      syncMetadataCompanyIdFkey: foreignKey({
        columns: [table.companyId],
        foreignColumns: [companies.id],
        name: "sync_metadata_company_id_fkey",
      }),
    };
  },
);

export const userTokens = pgTable(
  "user_tokens",
  {
    userId: uuid("user_id").defaultRandom(),
    refreshToken: text("refresh_token"),
    createdAt: timestamp("created_at", { withTimezone: true, mode: "string" })
      .defaultNow()
      .notNull(),
    updatedAt: timestamp("updated_at", {
      withTimezone: true,
      mode: "string",
    }).notNull(),
  },
  (table) => {
    return {
      userTokensUserIdFkey: foreignKey({
        columns: [table.userId],
        foreignColumns: [users.id],
        name: "user_tokens_user_id_fkey",
      }),
    };
  },
);

export const accounts = pgTable(
  "accounts",
  {
    id: uuid("id").defaultRandom().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", {
      mode: "string",
    }),
    refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
      mode: "string",
    }),
    scope: text("scope"),
    password: text("password"),
    createdAt: timestamp("created_at", { withTimezone: true, mode: "string" })
      .default(sql`CURRENT_TIMESTAMP`)
      .notNull(),
    updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" })
      .default(sql`CURRENT_TIMESTAMP`)
      .notNull(),
  },
  (table) => {
    return {
      fkAccountsUser: foreignKey({
        columns: [table.userId],
        foreignColumns: [users.id],
        name: "fk_accounts_user",
      }).onDelete("cascade"),
    };
  },
);

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

export const sessions = pgTable(
  "sessions",
  {
    id: uuid("id").defaultRandom().primaryKey().notNull(),
    expiresAt: timestamp("expires_at", { mode: "string" }).notNull(),
    token: text("token").notNull(),
    createdAt: timestamp("created_at", { mode: "string" })
      .default(sql`CURRENT_TIMESTAMP`)
      .notNull(),
    updatedAt: timestamp("updated_at", { mode: "string" })
      .default(sql`CURRENT_TIMESTAMP`)
      .notNull(),
    ipAddress: text("ip_address"),
    userAgent: text("user_agent"),
    userId: uuid("user_id").notNull(),
  },
  (table) => {
    return {
      fkSessionsUser: foreignKey({
        columns: [table.userId],
        foreignColumns: [users.id],
        name: "fk_sessions_user",
      }).onDelete("cascade"),
      sessionsTokenUnique: unique("sessions_token_unique").on(table.token),
    };
  },
);
@RattyJhay commented on GitHub (Feb 12, 2025): @Bekacru auth-schema.ts ``` import { pgTable, text, integer, 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(), emailVerified: boolean("email_verified").notNull(), image: text("image"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), firstName: text("first_name"), lastName: text("last_name"), phoneNumber: text("phone_number"), roleId: integer("role_id"), companyId: text("company_id"), }); export const sessions = pgTable("sessions", { id: text("id").primaryKey(), expiresAt: timestamp("expires_at").notNull(), token: text("token").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(() => users.id), }); export const accounts = pgTable("accounts", { id: text("id").primaryKey(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: text("user_id") .notNull() .references(() => users.id), 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 verifications = pgTable("verifications", { id: text("id").primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at"), updatedAt: timestamp("updated_at"), }); ``` my drizzle schema pulled from supabase: ``` export const users = pgTable( "users", { id: uuid("id").defaultRandom().primaryKey().notNull(), firstName: varchar("first_name").notNull(), lastName: varchar("last_name").notNull(), email: varchar("email").notNull(), password: text("password"), roleId: integer("role_id").notNull(), companyId: uuid("company_id").defaultRandom(), createdAt: timestamp("created_at", { withTimezone: true, mode: "string", }).defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string", }).defaultNow(), phoneNumber: varchar("phone_number").notNull(), name: text("name"), emailVerified: boolean("email_verified").default(false), image: text("image"), }, (table) => { return { usersCompanyIdFkey: foreignKey({ columns: [table.companyId], foreignColumns: [companies.id], name: "users_company_id_fkey", }) .onUpdate("cascade") .onDelete("set null"), usersCompanyIdFkey1: foreignKey({ columns: [table.companyId], foreignColumns: [companies.id], name: "users_company_id_fkey1", }) .onUpdate("cascade") .onDelete("set null"), usersEmailKey: unique("users_email_key").on(table.email), }; }, ) as ReturnType<typeof pgTable>; export const syncMetadata = pgTable( "sync_metadata", { id: uuid("id").defaultRandom().primaryKey().notNull(), branchId: uuid("branch_id"), companyId: uuid("company_id"), lastSyncTimestamp: timestamp("last_sync_timestamp", { withTimezone: true, mode: "string", }).defaultNow(), syncStatus: text("sync_status").default("active"), clientIdentifier: text("client_identifier"), createdAt: timestamp("created_at", { withTimezone: true, mode: "string", }).defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string", }).defaultNow(), }, (table) => { return { idxSyncMetadataBranch: index("idx_sync_metadata_branch").using( "btree", table.branchId.asc().nullsLast(), ), idxSyncMetadataCompany: index("idx_sync_metadata_company").using( "btree", table.companyId.asc().nullsLast(), ), syncMetadataBranchIdFkey: foreignKey({ columns: [table.branchId], foreignColumns: [branches.id], name: "sync_metadata_branch_id_fkey", }), syncMetadataCompanyIdFkey: foreignKey({ columns: [table.companyId], foreignColumns: [companies.id], name: "sync_metadata_company_id_fkey", }), }; }, ); export const userTokens = pgTable( "user_tokens", { userId: uuid("user_id").defaultRandom(), refreshToken: text("refresh_token"), createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) .defaultNow() .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string", }).notNull(), }, (table) => { return { userTokensUserIdFkey: foreignKey({ columns: [table.userId], foreignColumns: [users.id], name: "user_tokens_user_id_fkey", }), }; }, ); export const accounts = pgTable( "accounts", { id: uuid("id").defaultRandom().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", { mode: "string", }), refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { mode: "string", }), scope: text("scope"), password: text("password"), createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), }, (table) => { return { fkAccountsUser: foreignKey({ columns: [table.userId], foreignColumns: [users.id], name: "fk_accounts_user", }).onDelete("cascade"), }; }, ); export const verifications = pgTable("verifications", { id: uuid("id").defaultRandom().primaryKey().notNull(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at", { mode: "string" }).notNull(), createdAt: timestamp("created_at", { mode: "string" }).default( sql`CURRENT_TIMESTAMP`, ), updatedAt: timestamp("updated_at", { mode: "string" }).default( sql`CURRENT_TIMESTAMP`, ), }); export const sessions = pgTable( "sessions", { id: uuid("id").defaultRandom().primaryKey().notNull(), expiresAt: timestamp("expires_at", { mode: "string" }).notNull(), token: text("token").notNull(), createdAt: timestamp("created_at", { mode: "string" }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at", { mode: "string" }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: uuid("user_id").notNull(), }, (table) => { return { fkSessionsUser: foreignKey({ columns: [table.userId], foreignColumns: [users.id], name: "fk_sessions_user", }).onDelete("cascade"), sessionsTokenUnique: unique("sessions_token_unique").on(table.token), }; }, ); ```
Author
Owner

@focux commented on GitHub (Feb 24, 2025):

I upgraded from ^1.1.14 to 1.2.0-beta.15 and started getting this error when sign in with google provider:

# SERVER_ERROR:  TypeError: value.toISOString is not a function

      at PgTimestamp.mapToDriverValue
  (file:///Users/[redacted]/Projects/[redacted]/node_modules/.pnpm/drizzle-orm@0.38.4_patch_hash=510c14464759e68b069e30b07d4f455b4c3202c28ba427fd2263e9873_369d5bc8e0c5772244bc3ebda045a281/node_modules/src/pg-core/columns/timestamp.ts:66:16)
@focux commented on GitHub (Feb 24, 2025): I upgraded from `^1.1.14` to `1.2.0-beta.15` and started getting this error when sign in with google provider: ``` # SERVER_ERROR: TypeError: value.toISOString is not a function at PgTimestamp.mapToDriverValue (file:///Users/[redacted]/Projects/[redacted]/node_modules/.pnpm/drizzle-orm@0.38.4_patch_hash=510c14464759e68b069e30b07d4f455b4c3202c28ba427fd2263e9873_369d5bc8e0c5772244bc3ebda045a281/node_modules/src/pg-core/columns/timestamp.ts:66:16) ```
Author
Owner

@namuorg commented on GitHub (Mar 8, 2025):

I believe the issue here is that better-auth assumes that timestamp columns will be returned as Date objects from the Drizzle adapter, which is not true when { mode: "string" } is used.

In my project, I use { mode: "string" } for all timestamp columns and I'm running into problems like the ones mentioned in this issue. Using database hooks to convert timestamp fields like createdAt and updatedAt from Date to string seems like a bad idea because better-auth performs operations on timestamp fields assuming that they're Date objects which can lead to unexpected runtime errors.

I'd love for better-auth's Drizzle adapter to support Drizzle schemas where timestamp fields are using { mode: "string" }. Thanks!

@namuorg commented on GitHub (Mar 8, 2025): I believe the issue here is that `better-auth` assumes that `timestamp` columns will be returned as `Date` objects from the Drizzle adapter, which is not true when [`{ mode: "string" }`](https://orm.drizzle.team/docs/column-types/pg#timestamp) is used. In my project, I use `{ mode: "string" }` for all `timestamp` columns and I'm running into problems like the ones mentioned in this issue. Using database hooks to convert timestamp fields like `createdAt` and `updatedAt` from `Date` to `string` seems like a bad idea because `better-auth` performs operations on `timestamp` fields assuming that they're `Date` objects which can lead to unexpected runtime errors. I'd love for `better-auth`'s Drizzle adapter to support Drizzle schemas where `timestamp` fields are using `{ mode: "string" }`. Thanks!
Author
Owner

@dosubot[bot] commented on GitHub (Jun 14, 2025):

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

Issue Summary:

  • You reported TypeErrors with Better Auth's handling of date types when used with Supabase and Drizzle ORM.
  • Bekacru requested the drizzle auth schema, which you provided, highlighting differences in timestamp handling.
  • Focux experienced similar errors after upgrading to a newer version.
  • Namukang identified the root cause as Better Auth's assumption about timestamp columns and suggested supporting Drizzle's { mode: "string" } setting.

Next Steps:

  • Please confirm if this issue is still relevant to the latest version of the better-auth repository by commenting here.
  • If no updates are provided, the issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

@dosubot[bot] commented on GitHub (Jun 14, 2025): Hi, @RattyJhay. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog. I'm marking this issue as stale. **Issue Summary:** - You reported TypeErrors with Better Auth's handling of date types when used with Supabase and Drizzle ORM. - Bekacru requested the drizzle auth schema, which you provided, highlighting differences in timestamp handling. - Focux experienced similar errors after upgrading to a newer version. - Namukang identified the root cause as Better Auth's assumption about timestamp columns and suggested supporting Drizzle's `{ mode: "string" }` setting. **Next Steps:** - Please confirm if this issue is still relevant to the latest version of the better-auth repository by commenting here. - If no updates are provided, the issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Author
Owner

@namuorg commented on GitHub (Jun 15, 2025):

Yes, this issue is still relevant.

@namuorg commented on GitHub (Jun 15, 2025): Yes, this issue is still relevant.
Author
Owner

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

Also just ran into this issue and not sure how to handle. It makes far more sense for my code to handle date conversion in the frontend, I migrated everything and now Better Auth is crashing.

@steipete commented on GitHub (Aug 28, 2025): Also just ran into this issue and not sure how to handle. It makes far more sense for my code to handle date conversion in the frontend, I migrated everything and now Better Auth is crashing.
Author
Owner

@ping-maxwell commented on GitHub (Sep 30, 2025):

Hello all, can someone confirm that this issue still persists?

@ping-maxwell commented on GitHub (Sep 30, 2025): Hello all, can someone confirm that this issue still persists?
Author
Owner

@ping-maxwell commented on GitHub (Oct 3, 2025):

This PR should resolve anyone who is running into value.toIsoString() issues:
https://github.com/better-auth/better-auth/pull/5042

@ping-maxwell commented on GitHub (Oct 3, 2025): This PR should resolve anyone who is running into `value.toIsoString()` issues: https://github.com/better-auth/better-auth/pull/5042
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#660