Adding stripeClient from @better-auth/stripe/client to auth client removes twoFactorClient types from $ERROR_CODES #2076

Closed
opened 2026-03-13 09:25:12 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @EmilsValdmanis on GitHub (Oct 5, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When using the @better-auth/stripe plugin alongside the twoFactorClient plugin, the $ERROR_CODES type no longer includes the two-factor authentication error codes. This appears to be a type composition/merging issue between plugins.

  1. Create an auth client with only the twoFactorClient plugin:
import { createAuthClient } from "better-auth/react";
import { twoFactorClient } from "better-auth/client/plugins";

const authClient = createAuthClient({
    baseURL: "http://localhost:3000",
    basePath: "/api/auth",
    plugins: [
        twoFactorClient(),
    ],
});

export type AuthErrorCode = keyof (typeof authClient)["$ERROR_CODES"];
  1. Use the AuthErrorCode type to have type-safe translations for all errors:
const AUTH: Record<AuthErrorCode, string> = {
    // ... other error codes
    OTP_HAS_EXPIRED: "The code has expired",
    INVALID_TWO_FACTOR_COOKIE: "Invalid two-factor cookie",
    TWO_FACTOR_NOT_ENABLED: "Two-factor is not enabled",
    // ... etc - all two-factor error codes work fine
};
  1. Add the stripeClient plugin
import { createAuthClient } from "better-auth/react";
import { twoFactorClient } from "better-auth/client/plugins";
import { stripeClient } from "@better-auth/stripe/client";

const authClient = createAuthClient({
    baseURL: "http://localhost:3000",
    basePath: "/api/auth",
    plugins: [
        twoFactorClient(),
        stripeClient({
            subscription: true,
        }),
    ],
});

export type AuthErrorCode = keyof (typeof authClient)["$ERROR_CODES"];
  1. TypeScript now errors on two-factor error codes:

error TS2353: Object literal may only specify known properties, and 'OTP_HAS_EXPIRED' does not exist in type 'Record<...>'

Current vs. Expected behavior

Current Behavior

When stripeClient is added to the plugins array, the $ERROR_CODES type no longer includes error codes from twoFactorClient such as:

  • OTP_HAS_EXPIRED
  • OTP_NOT_ENABLED
  • INVALID_BACKUP_CODE
  • INVALID_TWO_FACTOR_COOKIE
  • TOTP_NOT_ENABLED
  • TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE
  • TWO_FACTOR_NOT_ENABLED
  • BACKUP_CODES_NOT_ENABLED
  • INVALID_CODE

Expected Behavior

The $ERROR_CODES type should be a union of all type and adding stripeClient plugin should not remove/invalidate twoFactorClient error codes from the type.

What version of Better Auth are you using?

1.3.8

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000",
    "release": "25.0.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.12 GB"
  },
  "node": {
    "version": "v22.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.1"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^15.5.0"
    },
    {
      "name": "react",
      "version": "19.1"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "^1.3.8",
    "config": null
  }
}

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

Types

Auth config (if applicable)


Additional context

No response

Originally created by @EmilsValdmanis on GitHub (Oct 5, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce When using the `@better-auth/stripe` plugin alongside the `twoFactorClient` plugin, the $ERROR_CODES type no longer includes the two-factor authentication error codes. This appears to be a type composition/merging issue between plugins. 1. Create an auth client with only the `twoFactorClient` plugin: ```typescript import { createAuthClient } from "better-auth/react"; import { twoFactorClient } from "better-auth/client/plugins"; const authClient = createAuthClient({ baseURL: "http://localhost:3000", basePath: "/api/auth", plugins: [ twoFactorClient(), ], }); export type AuthErrorCode = keyof (typeof authClient)["$ERROR_CODES"]; ``` 2. Use the `AuthErrorCode` type to have type-safe translations for all errors: ```typescript const AUTH: Record<AuthErrorCode, string> = { // ... other error codes OTP_HAS_EXPIRED: "The code has expired", INVALID_TWO_FACTOR_COOKIE: "Invalid two-factor cookie", TWO_FACTOR_NOT_ENABLED: "Two-factor is not enabled", // ... etc - all two-factor error codes work fine }; ``` 3. Add the `stripeClient` plugin ```typescript import { createAuthClient } from "better-auth/react"; import { twoFactorClient } from "better-auth/client/plugins"; import { stripeClient } from "@better-auth/stripe/client"; const authClient = createAuthClient({ baseURL: "http://localhost:3000", basePath: "/api/auth", plugins: [ twoFactorClient(), stripeClient({ subscription: true, }), ], }); export type AuthErrorCode = keyof (typeof authClient)["$ERROR_CODES"]; ``` 4. TypeScript now errors on two-factor error codes: `error TS2353: Object literal may only specify known properties, and 'OTP_HAS_EXPIRED' does not exist in type 'Record<...>'` ### Current vs. Expected behavior **Current Behavior** When `stripeClient` is added to the plugins array, the `$ERROR_CODES` type no longer includes error codes from `twoFactorClient` such as: - `OTP_HAS_EXPIRED` - `OTP_NOT_ENABLED` - `INVALID_BACKUP_CODE` - `INVALID_TWO_FACTOR_COOKIE` - `TOTP_NOT_ENABLED` - `TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE` - `TWO_FACTOR_NOT_ENABLED` - `BACKUP_CODES_NOT_ENABLED` - `INVALID_CODE` **Expected Behavior** The `$ERROR_CODES` type should be a union of all type and adding `stripeClient` plugin should not remove/invalidate twoFactorClient error codes from the type. ### What version of Better Auth are you using? 1.3.8 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000", "release": "25.0.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.12 GB" }, "node": { "version": "v22.14.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.1" }, "frameworks": [ { "name": "next", "version": "^15.5.0" }, { "name": "react", "version": "19.1" } ], "databases": null, "betterAuth": { "version": "^1.3.8", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Types ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
Author
Owner

@dosubot[bot] commented on GitHub (Oct 5, 2025):

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

This happens because the merging of $ERROR_CODES in Better Auth relies on each plugin providing a $ERROR_CODES property. The server-side twoFactor plugin does this, but the client-side twoFactorClient does not, and stripeClient also doesn't provide $ERROR_CODES. When you combine plugins, only those that explicitly provide $ERROR_CODES contribute to the merged type—so if one or more don't, TypeScript drops those error codes from the final type, which is why your two-factor error codes disappear when stripeClient is present. This is a known limitation in the current plugin type merging logic (source).

A common workaround is to extract your plugin configs into constants and manually merge their error code types to form a combined AuthErrorCode type (example). You can also use explicit type annotations or type assertions (like as ReturnType) to help TypeScript with inference, but this won't restore missing error codes if the plugins themselves don't expose them (source).

There isn't a built-in fix for this in v1.3.8. The plugins still work at runtime—this is a TypeScript type safety issue only (source). For now, manual merging or referencing the server-side auth config's $ERROR_CODES is the most reliable way to get a complete error code type.

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 (Oct 5, 2025): <!-- Greeting --> Hi @EmilsValdmanis! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This happens because the merging of $ERROR_CODES in Better Auth relies on each plugin providing a $ERROR_CODES property. The server-side twoFactor plugin does this, but the client-side twoFactorClient does not, and stripeClient also doesn't provide $ERROR_CODES. When you combine plugins, only those that explicitly provide $ERROR_CODES contribute to the merged type—so if one or more don't, TypeScript drops those error codes from the final type, which is why your two-factor error codes disappear when stripeClient is present. This is a known limitation in the current plugin type merging logic ([source](https://github.com/better-auth/better-auth/blob/ad59590e2491359a2787d75b3209b02351ff99de/packages/better-auth/src/types/plugins.ts)). A common workaround is to extract your plugin configs into constants and manually merge their error code types to form a combined AuthErrorCode type ([example](https://github.com/better-auth/better-auth/issues/1155#issuecomment-2699041689)). You can also use explicit type annotations or type assertions (like as ReturnType<typeof createAuthClient>) to help TypeScript with inference, but this won't restore missing error codes if the plugins themselves don't expose them ([source](https://github.com/better-auth/better-auth/issues/2123)). There isn't a built-in fix for this in v1.3.8. The plugins still work at runtime—this is a TypeScript type safety issue only ([source](https://github.com/better-auth/better-auth/issues/4279)). For now, manual merging or referencing the server-side auth config's $ERROR_CODES is the most reliable way to get a complete error code type. <!-- 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/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/fad938f3-df84-4d00-bfef-bf682d47bf51?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/5098)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2076