Strongly Typed Error Codes & Strict Hook Contexts #3011

Open
opened 2026-03-13 10:34:14 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @Wadiou on GitHub (Mar 7, 2026).

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

Currently, handling errors elegantly in the frontend using the better-auth client requires sacrificing type safety and relying on manual type casting. There are two main pain points hindering the developer experience:

  1. Generic string Error Codes: The error.code returned by the client methods is typed as a generic string | undefined. This prevents developers from leveraging TypeScript's autocomplete or exhaustiveness checking when handling specific error scenarios (e.g., mapping error codes to localized translation keys).
  2. Implicit any in onError hooks: When utilizing the built-in onError callback in the configuration options (e.g., authClient.requestPasswordReset(data, { onError: (context) => {} })), the context argument implicitly falls back to any. This completely strips away type safety, leaving the context.error object untyped and prone to runtime errors.

Describe the solution you'd like

  1. Union of String Literals: Narrow down the error.code type from a generic string to a standardized union of string literals representing known authentication error codes (e.g., 'USER_NOT_FOUND' | 'INVALID_PASSWORD' | 'TOKEN_EXPIRED' | ...).
  2. Strictly Typed Context Params: Apply strict interface typings to the context parameter within the onError and onSuccess hooks. The context.error parameter should inherently carry the typed HTTP error structure rather than delegating to any.

Describe alternatives you've considered

The only viable workaround right now is to manually circumvent TypeScript by type-casting the error object inside the callback context back to an expected shape (e.g., (context.error.code as MyErrorKeys)). This is an anti-pattern that creates brittle code if the underlying error structures change.

Additional context

Examples of the typing gaps:

// 1. Generic string issue:
const { error } = await authClient.requestPasswordReset(data);
// ^? `error.code` infers as `string | undefined` instead of a literal union.

// 2. Implicit `any` issue in hooks:
await authClient.requestPasswordReset(data, {
  onError: context => {
    // `context` is inferred as `any`
    // We instantly lose type checking and are forced to manually cast:
    const message = t(
      `Errors.${(context.error.code as keyof MyErrorKeys) || 'GENERIC_ERROR'}`,
    );
  },
});
Originally created by @Wadiou on GitHub (Mar 7, 2026). Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Currently, handling errors elegantly in the frontend using the `better-auth` client requires sacrificing type safety and relying on manual type casting. There are two main pain points hindering the developer experience: 1. **Generic `string` Error Codes:** The `error.code` returned by the client methods is typed as a generic `string | undefined`. This prevents developers from leveraging TypeScript's autocomplete or exhaustiveness checking when handling specific error scenarios (e.g., mapping error codes to localized translation keys). 2. **Implicit `any` in `onError` hooks:** When utilizing the built-in `onError` callback in the configuration options (e.g., `authClient.requestPasswordReset(data, { onError: (context) => {} })`), the `context` argument implicitly falls back to `any`. This completely strips away type safety, leaving the `context.error` object untyped and prone to runtime errors. ### Describe the solution you'd like 1. **Union of String Literals:** Narrow down the `error.code` type from a generic `string` to a standardized union of string literals representing known authentication error codes (e.g., `'USER_NOT_FOUND' | 'INVALID_PASSWORD' | 'TOKEN_EXPIRED' | ...`). 2. **Strictly Typed Context Params:** Apply strict interface typings to the `context` parameter within the `onError` and `onSuccess` hooks. The `context.error` parameter should inherently carry the typed HTTP error structure rather than delegating to `any`. ### Describe alternatives you've considered The only viable workaround right now is to manually circumvent TypeScript by type-casting the error object inside the callback context back to an expected shape (e.g., `(context.error.code as MyErrorKeys)`). This is an anti-pattern that creates brittle code if the underlying error structures change. ### Additional context Examples of the typing gaps: ```ts // 1. Generic string issue: const { error } = await authClient.requestPasswordReset(data); // ^? `error.code` infers as `string | undefined` instead of a literal union. // 2. Implicit `any` issue in hooks: await authClient.requestPasswordReset(data, { onError: context => { // `context` is inferred as `any` // We instantly lose type checking and are forced to manually cast: const message = t( `Errors.${(context.error.code as keyof MyErrorKeys) || 'GENERIC_ERROR'}`, ); }, }); ```
GiteaMirror added the enhancement label 2026-03-13 10:34:14 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#3011