api.signInEmailOTP tries to put javascript date object into sqlite if columns were renamed in config #1992

Closed
opened 2026-03-13 09:19:29 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @brachkow on GitHub (Sep 21, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Do the migration.
  2. Create backend with SQLite database coming from Kysely dialect. I'm encountering this issue with D1 dialect, but I was able to reproduce it with better-sqlite3 wrapped with SqliteAdapter.3. Add emailOTP plugin. Send code (sendVerificationOTP), and try to exchange it to token (api.signInEmailOTP). Issue should happen.

P.S. One thing I was unable to test, at the time of writing this issue is that I had this feature working, and, maybe coincidentally, it stopped working somewhere I renamed tables from singular to plural via SQL migration and

    session: {
      modelName: 'sessions',
    },

in better-auth config. But even if it so, i'm sure that it must work.

Current vs. Expected behavior

Current: any sqlite adapter shows an error (Error: D1_TYPE_ERROR: Type 'object' not supported for value 'Sun Sep 21 2025 23:11:43 GMT+0200 (Central European Summer Time)') telling that wrong data type was passed
Expected: better-auth will see that it is sqlite database, and will transform date object to a string

What version of Better Auth are you using?

1.3.13

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Mon Aug 25 21:17:51 PDT 2025; root:xnu-12377.1.9~3/RELEASE_ARM64_T6000",
    "release": "25.0.0",
    "cpuCount": 8,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.09 GB"
  },
  "node": {
    "version": "v22.17.1",
    "env": "development"
  },
  "packageManager": {
    "name": "pnpm",
    "version": "10.7.1"
  },
  "frameworks": null,
  "databases": [
    {
      "name": "better-sqlite3",
      "version": "^12.2.0"
    },
    {
      "name": "kysely",
      "version": "^0.28.7"
    }
  ],
  "betterAuth": {
    "version": "^1.3.13",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

// with D1
export const authService = () => {
  const dialect = getDialect();
  const { AUTH_SECRET: secret, AUTH_BASE_URL: baseURL } = getContext<App>().env;

  return betterAuth({
    database: dialect,
    secret,
    baseURL,
    user: {
      modelName: 'users',
      fields: {
        name: 'username',
      },
      additionalFields: {
        onlineAt: {
          type: 'string',
          required: false,
          input: false,
        },
        isOnboarded: {
          type: 'number',
          required: false,
          defaultValue: 0,
          input: false,
        },
      },
    },
    session: {
      modelName: 'sessions',
    },
    account: {
      modelName: 'accounts',
    },
    verification: {
      modelName: 'verifications',
    },
    plugins: [
      emailOTP({
        async sendVerificationOTP({ email, otp, type }) {
          if (type === 'sign-in') {
            await emailService.sendCodeViaEmail({ email, code: otp });
            // Send the OTP for sign in
          } else if (type === 'email-verification') {
            // Send the OTP for email verification
          } else {
            // Send the OTP for password reset
          }
        },
      }),
    ],
  });
};

// same but for better-sqlite3

export const auth = betterAuth({
  database: new Database(
    '/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/6e8fd(will cut this for potential privacy issues).sqlite',
  ),
  secret: process.env.AUTH_SECRET,
  baseURL: process.env.AUTH_BASE_URL,
  user: {
    modelName: 'users',
    fields: {
      name: 'username',
    },
    additionalFields: {
      onlineAt: {
        type: 'string',
        required: false,
        input: false,
      },
      isOnboarded: {
        type: 'number',
        required: false,
        defaultValue: 0,
        input: false,
      },
    },
  },
  session: {
    modelName: 'sessions',
  },
  account: {
    modelName: 'accounts',
  },
  verification: {
    modelName: 'verifications',
  },
  plugins: [
    emailOTP({
      async sendVerificationOTP({ email, otp, type }) {
        if (type === 'sign-in') {
          await emailService.sendCodeViaEmail({ email, code: otp });
          // Send the OTP for sign in
        } else if (type === 'email-verification') {
          // Send the OTP for email verification
        } else {
          // Send the OTP for password reset
        }
      },
    }),
  ],
});

export const authClient = createAuthClient({
  plugins: [emailOTPClient()],
});

Additional context

No response

Originally created by @brachkow on GitHub (Sep 21, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Do the migration. 2. Create backend with SQLite database coming from Kysely dialect. I'm encountering this issue with D1 dialect, but I was able to reproduce it with `better-sqlite3` wrapped with `SqliteAdapter`.3. Add `emailOTP` plugin. Send code (`sendVerificationOTP`), and try to exchange it to token (`api.signInEmailOTP`). Issue should happen. P.S. One thing I was unable to test, at the time of writing this issue is that I had this feature working, and, maybe coincidentally, it stopped working somewhere I renamed tables from singular to plural via SQL migration and ``` session: { modelName: 'sessions', }, ``` in better-auth config. But even if it so, i'm sure that it must work. ### Current vs. Expected behavior Current: any sqlite adapter shows an error (Error: D1_TYPE_ERROR: Type 'object' not supported for value 'Sun Sep 21 2025 23:11:43 GMT+0200 (Central European Summer Time)') telling that wrong data type was passed Expected: better-auth will see that it is sqlite database, and will transform date object to a string ### What version of Better Auth are you using? 1.3.13 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Mon Aug 25 21:17:51 PDT 2025; root:xnu-12377.1.9~3/RELEASE_ARM64_T6000", "release": "25.0.0", "cpuCount": 8, "cpuModel": "Apple M1 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.09 GB" }, "node": { "version": "v22.17.1", "env": "development" }, "packageManager": { "name": "pnpm", "version": "10.7.1" }, "frameworks": null, "databases": [ { "name": "better-sqlite3", "version": "^12.2.0" }, { "name": "kysely", "version": "^0.28.7" } ], "betterAuth": { "version": "^1.3.13", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript // with D1 export const authService = () => { const dialect = getDialect(); const { AUTH_SECRET: secret, AUTH_BASE_URL: baseURL } = getContext<App>().env; return betterAuth({ database: dialect, secret, baseURL, user: { modelName: 'users', fields: { name: 'username', }, additionalFields: { onlineAt: { type: 'string', required: false, input: false, }, isOnboarded: { type: 'number', required: false, defaultValue: 0, input: false, }, }, }, session: { modelName: 'sessions', }, account: { modelName: 'accounts', }, verification: { modelName: 'verifications', }, plugins: [ emailOTP({ async sendVerificationOTP({ email, otp, type }) { if (type === 'sign-in') { await emailService.sendCodeViaEmail({ email, code: otp }); // Send the OTP for sign in } else if (type === 'email-verification') { // Send the OTP for email verification } else { // Send the OTP for password reset } }, }), ], }); }; // same but for better-sqlite3 export const auth = betterAuth({ database: new Database( '/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/6e8fd(will cut this for potential privacy issues).sqlite', ), secret: process.env.AUTH_SECRET, baseURL: process.env.AUTH_BASE_URL, user: { modelName: 'users', fields: { name: 'username', }, additionalFields: { onlineAt: { type: 'string', required: false, input: false, }, isOnboarded: { type: 'number', required: false, defaultValue: 0, input: false, }, }, }, session: { modelName: 'sessions', }, account: { modelName: 'accounts', }, verification: { modelName: 'verifications', }, plugins: [ emailOTP({ async sendVerificationOTP({ email, otp, type }) { if (type === 'sign-in') { await emailService.sendCodeViaEmail({ email, code: otp }); // Send the OTP for sign in } else if (type === 'email-verification') { // Send the OTP for email verification } else { // Send the OTP for password reset } }, }), ], }); export const authClient = createAuthClient({ plugins: [emailOTPClient()], }); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 09:19:29 -05:00
Author
Owner

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

Hi @brachkow! I'm Dosu and I’m helping the better-auth team.

This error happens because the emailOTP plugin (and api.signInEmailOTP) generates JavaScript Date objects for fields like expiresAt, then passes them directly to the internal adapter, which tries to store them in SQLite. SQLite (and D1) can't store JavaScript Date objects—they expect strings or numbers, so you get a type error.

Better Auth does have a mechanism for serializing Date objects before storage: the adapter factory will convert Date objects to ISO strings if you set supportsDates: false in your adapter config. You can also use the customTransformInput hook in the adapter config to manually serialize Date fields before they hit the database. By default, though, supportsDates is true, so Dates are not transformed unless you change this setting. See adapter factory code.

Workaround:
Patch your adapter config to set supportsDates: false, or provide a customTransformInput function that serializes Date fields (e.g., with .toISOString() or .getTime()). This will ensure Dates are stored as strings or numbers, which SQLite supports.

Renaming tables (singular/plural) shouldn't affect Date serialization, but if your migration changed column types or default values, double-check that your schema expects strings/numbers for date fields.

Let me know if you want a code snippet for the adapter config!

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 21, 2025): <!-- Greeting --> Hi @brachkow! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This error happens because the emailOTP plugin (and api.signInEmailOTP) generates JavaScript Date objects for fields like `expiresAt`, then passes them directly to the internal adapter, which tries to store them in SQLite. SQLite (and D1) can't store JavaScript Date objects—they expect strings or numbers, so you get a type error. Better Auth does have a mechanism for serializing Date objects before storage: the adapter factory will convert Date objects to ISO strings if you set `supportsDates: false` in your adapter config. You can also use the `customTransformInput` hook in the adapter config to manually serialize Date fields before they hit the database. By default, though, `supportsDates` is `true`, so Dates are not transformed unless you change this setting. [See adapter factory code](https://github.com/better-auth/better-auth/blob/1baaccbf52803d27046dce3da57c5c7201a020fb/packages/better-auth/src/adapters/adapter-factory/index.ts). **Workaround:** Patch your adapter config to set `supportsDates: false`, or provide a `customTransformInput` function that serializes Date fields (e.g., with `.toISOString()` or `.getTime()`). This will ensure Dates are stored as strings or numbers, which SQLite supports. Renaming tables (singular/plural) shouldn't affect Date serialization, but if your migration changed column types or default values, double-check that your schema expects strings/numbers for date fields. Let me know if you want a code snippet for the adapter config! <!-- 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/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/452fc0d7-e1c3-4173-97ea-0e3e42070910?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/4805)
Author
Owner

@brachkow commented on GitHub (Sep 26, 2025):

Sorry, saw that I didn't include proper client in issue description.

@brachkow commented on GitHub (Sep 26, 2025): Sorry, saw that I didn't include proper client in issue description.
Author
Owner

@brachkow commented on GitHub (Sep 26, 2025):

I did some further investigation, and can confirm that issue only happens if columns were renamed. I was able to pass this issue if I don't alter table inside client.

@brachkow commented on GitHub (Sep 26, 2025): I did some further investigation, and can confirm that issue only happens if columns were renamed. I was able to pass this issue if I don't alter table inside client.
Author
Owner

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

@brachkow Can you confirm the issue still persists on latest?

@ping-maxwell commented on GitHub (Oct 6, 2025): @brachkow Can you confirm the issue still persists on latest?
Author
Owner

@brachkow commented on GitHub (Oct 6, 2025):

@ping-maxwell just started from scratch and was able to reproduce this error both on kysely D1 Dialect and better-sqlite on "better-auth": "^1.3.26",

@brachkow commented on GitHub (Oct 6, 2025): @ping-maxwell just started from scratch and was able to reproduce this error both on kysely D1 Dialect and better-sqlite on "better-auth": "^1.3.26",
Author
Owner

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

In the initial post, you mentioned you wrapped it with the SqliteAdapter?

@ping-maxwell commented on GitHub (Oct 7, 2025): In the initial post, you mentioned you wrapped it with the `SqliteAdapter`?
Author
Owner

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

I just tested locally and everything works.
Using better-sqlite3 and directly passing it straight to BA:

Image

Also have a custom model name for session:

Image

(And ran migrations as I should)

Then went through the email otp flow, got the OTP then used the signIn.emailOtp and everything works on my end.

@ping-maxwell commented on GitHub (Oct 7, 2025): I just tested locally and everything works. Using `better-sqlite3` and directly passing it straight to BA: <img width="503" height="144" alt="Image" src="https://github.com/user-attachments/assets/57930951-4118-4170-aa86-ee4effa02a7d" /> Also have a custom model name for `session`: <img width="397" height="28" alt="Image" src="https://github.com/user-attachments/assets/da72636c-6e4b-4b29-99d3-a4c4098b87a3" /> (And ran migrations as I should) Then went through the email otp flow, got the OTP then used the `signIn.emailOtp` and everything works on my end.
Author
Owner

@brachkow commented on GitHub (Oct 7, 2025):

@ping-maxwell I tired your example, and with only sessions table renamed it works.

But as soon as I add more renames, like in OP error appears. It seems issue is with verifications table at least — error appears if its renamed.

@brachkow commented on GitHub (Oct 7, 2025): @ping-maxwell I tired your example, and with only sessions table renamed it works. But as soon as I add more renames, like in OP error appears. It seems issue is with `verifications` table at least — error appears if its renamed.
Author
Owner

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

@ping-maxwell I tired your example, and with only sessions table renamed it works.

But as soon as I add more renames, like in OP error appears. It seems issue is with verifications table at least — error appears if its renamed.

Gotcha, will test it

@ping-maxwell commented on GitHub (Oct 7, 2025): > [@ping-maxwell](https://github.com/ping-maxwell) I tired your example, and with only sessions table renamed it works. > > But as soon as I add more renames, like in OP error appears. It seems issue is with `verifications` table at least — error appears if its renamed. Gotcha, will test it
Author
Owner

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

Found the issue, working on a fix.

@ping-maxwell commented on GitHub (Oct 7, 2025): Found the issue, working on a fix.
Author
Owner

@dnyg commented on GitHub (Oct 22, 2025):

Any update on this?

@dnyg commented on GitHub (Oct 22, 2025): Any update on this?
Author
Owner

@brachkow commented on GitHub (Oct 22, 2025):

I beleive it is fixed now but stuck somewhere in the canary:( At least I don't see this change in changelog of the latest version

@brachkow commented on GitHub (Oct 22, 2025): I beleive it is fixed now but stuck somewhere in the canary:( At least I don't see this change in changelog of the latest version
Author
Owner

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

Got to wait on release or canary

@ping-maxwell commented on GitHub (Oct 22, 2025): Got to wait on release or canary
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1992