Custom error messages #1097

Closed
opened 2026-03-13 08:22:20 -05:00 by GiteaMirror · 10 comments
Owner

Originally created by @linhpncute on GitHub (Apr 22, 2025).

Originally assigned to: @ping-maxwell on GitHub.

Is this suited for github?

  • Yes, this is suited for github

Hi the better-auth community, I'm using a custom message using an after hook, but I was wondering if there was another way to be able to custom message the error myself?

lib/auth.ts

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg',
    schema: { user, session, account, verification, twoFactorTable },
    debugLogs: true,
  }),

  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,

  emailAndPassword: {
    enabled: true,
    autoSignIn: true,
  },

  hooks: {
    after: createAuthMiddleware(async (ctx) => {
      const path = ctx.path;
      const response = ctx.context.returned as APIError;
      if (
        path.startsWith('/sign-up') &&
        response.body?.code === 'USER_ALREADY_EXISTS'
      ) {
        throw new APIError('BAD_REQUEST', {
          ...response.body,
          message: 'Tài khoản đã tồn tại', // account already exists
        });
      }
      if (
        path.startsWith('/sign-in') &&
        response.body?.code === 'INVALID_EMAIL_OR_PASSWORD'
      ) {
        throw new APIError('UNAUTHORIZED', {
          ...response.body,
          message: 'Tên đăng nhập hoặc mật khẩu không hợp lệ', // invalid email or password
        });
      }
      if(path.startsWith('/change-password') && response.body?.code === 'INVALID_PASSWORD'){
        throw new APIError('BAD_REQUEST', {
          ...response.body,
          message: "Mật khẩu hiện tại không hợp lệ" //current password is invalid
        })
      }
    }),
  },

  plugins: [
    admin(),
    twoFactor(),
    captcha({
      provider: 'hcaptcha',
      siteKey: '20000000-ffff-ffff-ffff-000000000002', //test sitekey
      secretKey: '0x0000000000000000000000000000000000000000', //test secret
    }),
  ],
});

Describe the solution you'd like

Custom message for error response

Describe alternatives you've considered

Custom message for error response without using after hook

Additional context

No response

Originally created by @linhpncute on GitHub (Apr 22, 2025). Originally assigned to: @ping-maxwell on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Hi the better-auth community, I'm using a custom message using an after hook, but I was wondering if there was another way to be able to custom message the error myself? ``` lib/auth.ts export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema: { user, session, account, verification, twoFactorTable }, debugLogs: true, }), secret: process.env.BETTER_AUTH_SECRET, baseURL: process.env.BETTER_AUTH_URL, emailAndPassword: { enabled: true, autoSignIn: true, }, hooks: { after: createAuthMiddleware(async (ctx) => { const path = ctx.path; const response = ctx.context.returned as APIError; if ( path.startsWith('/sign-up') && response.body?.code === 'USER_ALREADY_EXISTS' ) { throw new APIError('BAD_REQUEST', { ...response.body, message: 'Tài khoản đã tồn tại', // account already exists }); } if ( path.startsWith('/sign-in') && response.body?.code === 'INVALID_EMAIL_OR_PASSWORD' ) { throw new APIError('UNAUTHORIZED', { ...response.body, message: 'Tên đăng nhập hoặc mật khẩu không hợp lệ', // invalid email or password }); } if(path.startsWith('/change-password') && response.body?.code === 'INVALID_PASSWORD'){ throw new APIError('BAD_REQUEST', { ...response.body, message: "Mật khẩu hiện tại không hợp lệ" //current password is invalid }) } }), }, plugins: [ admin(), twoFactor(), captcha({ provider: 'hcaptcha', siteKey: '20000000-ffff-ffff-ffff-000000000002', //test sitekey secretKey: '0x0000000000000000000000000000000000000000', //test secret }), ], }); ``` ### Describe the solution you'd like Custom message for error response ### Describe alternatives you've considered Custom message for error response without using after hook ### Additional context _No response_
GiteaMirror added the question label 2026-03-13 08:22:20 -05:00
Author
Owner

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

Not as of now unfortunately. Your solution good.

A lot of users translate the error not using the hook, and just directly translate it in the front-end, but it's personal preference.

@ping-maxwell commented on GitHub (Apr 22, 2025): Not as of now unfortunately. Your solution good. A lot of users translate the error not using the hook, and just directly translate it in the front-end, but it's personal preference.
Author
Owner

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

It would be great if we support much better way at this.
The primary thing stopping us is we need to plan for good support for multi-language errors 🤔

@ping-maxwell commented on GitHub (Apr 22, 2025): It would be great if we support much better way at this. The primary thing stopping us is we need to plan for good support for multi-language errors 🤔
Author
Owner

@erickweil commented on GitHub (Jun 26, 2025):

I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a onAPIError option but I tried every combination without success.

Then doing as above (after hook), only works for some errors, for example, if you call POST /api/auth/sign-in/email with a invalid callbackURL in the body, the error returned isn't catched.

My scenario is that I want a common schema all error responses follow, both from better auth and from my API, so I need to catch every error and re-send it after this remapping.

@erickweil commented on GitHub (Jun 26, 2025): I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a [onAPIError](https://www.better-auth.com/docs/reference/options#onapierror) option but I tried every combination without success. Then doing as above (after hook), only works for some errors, for example, if you call POST /api/auth/sign-in/email with a invalid callbackURL in the body, the error returned isn't catched. My scenario is that I want a common schema all error responses follow, both from better auth and from my API, so I need to catch every error and re-send it after this remapping.
Author
Owner

@irg1008 commented on GitHub (Jul 2, 2025):

Also with phoneNumber and several plugins the callback it's not even called, not once. And afert hooks breaks

@irg1008 commented on GitHub (Jul 2, 2025): Also with phoneNumber and several plugins the callback it's not even called, not once. And afert hooks breaks
Author
Owner

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

I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a onAPIError option but I tried every combination without success.

Yeah the onAPIError is an existing bug unfortunately

@ping-maxwell commented on GitHub (Jul 3, 2025): > I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a [onAPIError](https://www.better-auth.com/docs/reference/options#onapierror) option but I tried every combination without success. Yeah the onAPIError is an existing bug unfortunately
Author
Owner

@ianriizky commented on GitHub (Aug 21, 2025):

I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a onAPIError option but I tried every combination without success.

Yeah the onAPIError is an existing bug unfortunately

I think this bug happened since the better-call package used for the route handler doesn't use the onError as we expected. Here I create a PR on its package to fix this error (https://github.com/Bekacru/better-call/pull/42). Hopefully, it will also help the ongoing bug int BA package as well.

@ianriizky commented on GitHub (Aug 21, 2025): > > I was trying to catch better auth ApiError's and what confused me is that according to the docs reference there is a [onAPIError](https://www.better-auth.com/docs/reference/options#onapierror) option but I tried every combination without success. > > Yeah the onAPIError is an existing bug unfortunately I think this bug happened since the better-call package used for the route handler doesn't use the `onError` as we expected. Here I create a PR on its package to fix this error (https://github.com/Bekacru/better-call/pull/42). Hopefully, it will also help the ongoing bug int BA package as well.
Author
Owner

@dosubot[bot] commented on GitHub (Nov 20, 2025):

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

Issue Summary:

  • You requested the ability to customize error messages directly in error responses rather than using after hooks.
  • The maintainer acknowledged this feature is not currently supported but plans improvements in error handling for version 2.0, including multi-language support.
  • Users have reported bugs with the onAPIError option and inconsistent error catching.
  • A related PR was submitted to fix routing handler bugs that affect error handling.
  • The discussion reflects ongoing efforts to enhance error customization and handling in future releases.

Next Steps:

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting here.
  • If I don’t hear from you, this issue will be automatically closed in 7 days.

Thanks for your understanding and contribution!

@dosubot[bot] commented on GitHub (Nov 20, 2025): Hi, @linhpncute. 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 requested the ability to customize error messages directly in error responses rather than using after hooks. - The maintainer acknowledged this feature is not currently supported but plans improvements in error handling for version 2.0, including multi-language support. - Users have reported bugs with the onAPIError option and inconsistent error catching. - A related PR was submitted to fix routing handler bugs that affect error handling. - The discussion reflects ongoing efforts to enhance error customization and handling in future releases. **Next Steps:** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting here. - If I don’t hear from you, this issue will be automatically closed in 7 days. Thanks for your understanding and contribution!
Author
Owner

@kononoVikaReal commented on GitHub (Dec 6, 2025):

Is there a better solution at the moment than what linhpncute suggested? 🤔

@kononoVikaReal commented on GitHub (Dec 6, 2025): Is there a better solution at the moment than what linhpncute suggested? 🤔
Author
Owner

@aow3xm commented on GitHub (Dec 7, 2025):

Is there a better solution at the moment than what linhpncute suggested? 🤔

I created a custom object called AUTH_ERROR_TRANSLATE, this object will have the key as the AuthErrorCode and the value as the custom message you want to translate. Then, in the after hook, you just need to check if the error is an instance of APIError and then map it out. I implemented it like this:

import { APIError } from "better-auth/api";
export const auth = betterAuth({
   ...
   hooks: {
        after: createAuthMiddleware(async (ctx) => {
            const error = ctx.context.returned;
            if (error instanceof APIError) {
                const message =
                    error.body?.code && error.body.code in AUTH_ERROR_TRANSLATE
                        ? AUTH_ERROR_TRANSLATE[error.body.code as AuthErrorCode]
                        : error.message;

                throw new APIError(error.status, {
                    ...error.body,
                    message,
                });
            }
        }),
    },
   ...
});

type AuthErrorCode = keyof typeof auth.$ERROR_CODES;
const AUTH_ERROR_TRANSLATE: Partial<Record<AuthErrorCode, string>> = {
    USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: "Email này đã được đăng ký với tài khoản khác",
   //...other custom errors
} as const;
@aow3xm commented on GitHub (Dec 7, 2025): > Is there a better solution at the moment than what linhpncute suggested? 🤔 I created a custom object called AUTH_ERROR_TRANSLATE, this object will have the key as the AuthErrorCode and the value as the custom message you want to translate. Then, in the after hook, you just need to check if the error is an instance of APIError and then map it out. I implemented it like this: ```typescript import { APIError } from "better-auth/api"; export const auth = betterAuth({ ... hooks: { after: createAuthMiddleware(async (ctx) => { const error = ctx.context.returned; if (error instanceof APIError) { const message = error.body?.code && error.body.code in AUTH_ERROR_TRANSLATE ? AUTH_ERROR_TRANSLATE[error.body.code as AuthErrorCode] : error.message; throw new APIError(error.status, { ...error.body, message, }); } }), }, ... }); type AuthErrorCode = keyof typeof auth.$ERROR_CODES; const AUTH_ERROR_TRANSLATE: Partial<Record<AuthErrorCode, string>> = { USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: "Email này đã được đăng ký với tài khoản khác", //...other custom errors } as const; ```
Author
Owner

@darseen commented on GitHub (Dec 8, 2025):

@aow3xm great idea, thank you! Although the message type was string | undefined, you can easly fix it(and simplify it) like this:

after: createAuthMiddleware(async (ctx) => {
      const error = ctx.context.returned;

      if (error instanceof APIError) {
        const message =
          AUTH_ERROR_TRANSLATIONS[error.body?.code as AuthErrorCode] ||
          error.message;

        throw new APIError(error.status, {
          ...error.body,
          message,
        });
      }
    }),
@darseen commented on GitHub (Dec 8, 2025): @aow3xm great idea, thank you! Although the message type was `string | undefined`, you can easly fix it(and simplify it) like this: ```ts after: createAuthMiddleware(async (ctx) => { const error = ctx.context.returned; if (error instanceof APIError) { const message = AUTH_ERROR_TRANSLATIONS[error.body?.code as AuthErrorCode] || error.message; throw new APIError(error.status, { ...error.body, message, }); } }), ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1097