[GH-ISSUE #7057] onEmailVerification, afterEmailVerification hooks are never called when verifying email if the request has "updateTo" field #19349

Closed
opened 2026-04-15 18:20:15 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @techrome on GitHub (Dec 30, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7057

Originally assigned to: @bytaesu on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

In this file:
https://github.com/better-auth/better-auth/blob/canary/packages/better-auth/src/api/routes/email-verification.ts
("better-auth\dist\api\routes\email-verification.mjs")

When the request has the field "updateTo" (which I assume gets assigned whenever we change email to another email), there's a big if statement

16ce94bde2/packages/better-auth/src/api/routes/email-verification.ts (L311)

which never runs any hook below it (onEmailVerification, afterEmailVerification) because it always returns something, like here:

16ce94bde2/packages/better-auth/src/api/routes/email-verification.ts (L434-L445)

Current vs. Expected behavior

Currently the hooks don't run, but I would expect them to run. However I'm not sure whether it was done intentionally because of some edge case.

What version of Better Auth are you using?

1.4.7

System info

"betterAuth": {
    "version": "^1.4.7",
    "config": null
  }

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

Backend

Auth config (if applicable)


Additional context

No response

Originally created by @techrome on GitHub (Dec 30, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7057 Originally assigned to: @bytaesu on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce In this file: https://github.com/better-auth/better-auth/blob/canary/packages/better-auth/src/api/routes/email-verification.ts ("better-auth\dist\api\routes\email-verification.mjs") When the request has the field "updateTo" (which I assume gets assigned whenever we change email to another email), there's a big if statement https://github.com/better-auth/better-auth/blob/16ce94bde27ea316d03357fd1f0668e56bb3e06c/packages/better-auth/src/api/routes/email-verification.ts#L311 which never runs any hook below it (onEmailVerification, afterEmailVerification) because it always returns something, like here: https://github.com/better-auth/better-auth/blob/16ce94bde27ea316d03357fd1f0668e56bb3e06c/packages/better-auth/src/api/routes/email-verification.ts#L434-L445 ### Current vs. Expected behavior Currently the hooks don't run, but I would expect them to run. However I'm not sure whether it was done intentionally because of some edge case. ### What version of Better Auth are you using? 1.4.7 ### System info ```bash "betterAuth": { "version": "^1.4.7", "config": null } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-15 18:20:15 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 30, 2025):

It seems that your observation regarding the onEmailVerification and afterEmailVerification hooks not being triggered when the request includes the "updateTo" field is valid. The relevant code appears to return early, thus skipping the hook executions completely when the update is applied.

There is a similar issue that has been discussed regarding the execution of hooks, suggesting there may need to be improvements in how these hooks are triggered to ensure they are called as expected for email changes [1].

As a next step, I recommend opening a GitHub issue or adding further details to the existing relevant issues on GitHub discussing this behavior. You can also tag the Better Auth team members, @bekacru or @ping__, for insights or updates regarding this specific behavior in your case.

Is there anything else you would like to explore on this topic?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3698417484 --> @better-auth-agent[bot] commented on GitHub (Dec 30, 2025): It seems that your observation regarding the `onEmailVerification` and `afterEmailVerification` hooks not being triggered when the request includes the "updateTo" field is valid. The relevant code appears to return early, thus skipping the hook executions completely when the update is applied. There is a similar issue that has been discussed regarding the execution of hooks, suggesting there may need to be improvements in how these hooks are triggered to ensure they are called as expected for email changes [[1]](https://github.com/better-auth/better-auth/issues/7002). As a next step, I recommend opening a GitHub issue or adding further details to the existing relevant issues on GitHub discussing this behavior. You can also tag the Better Auth team members, @bekacru or @ping__, for insights or updates regarding this specific behavior in your case. Is there anything else you would like to explore on this topic? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@techrome commented on GitHub (Dec 30, 2025):

Update: don't use this workaround, use the hooks instead in my post below
As a workaround I'm currently using a postgres db trigger. My use case was that I created 2 additional fields "pendingEmail" (string) and "pendingEmailSetAt" (date) for the user table. That was done because I allow sign up without requiring email and put a dummy unique mail inside the email column just to keep better-auth validation happy. Then if the user supplied an email, I set it to the pendingEmail column and send the mail. After that, when the user verifies it, the pendingEmail gets moved into email column. But the issue was that I needed to clear pendingEmail after that and that's why I needed the hook.

So the current solution for me is to create a trigger like this:

CREATE OR REPLACE FUNCTION clear_pending_email_on_verified()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
  IF NEW."email_verified" = TRUE
     AND NEW."pending_email" IS NOT NULL
     AND NEW."pending_email" = NEW."email"
  THEN
    NEW."pending_email" := NULL;
    NEW."pending_email_set_at" := NULL;
  END IF;

  RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS trigger_clear_pending_email_on_verified ON "user";

CREATE TRIGGER trigger_clear_pending_email_on_verified
BEFORE UPDATE OF "email", "email_verified"
ON "user"
FOR EACH ROW
EXECUTE FUNCTION clear_pending_email_on_verified();

This works fine for now but since I'm new to this I would like to hear any feedback whether I'm doing something stupid and maybe there's an already established way to handle such scenario.

<!-- gh-comment-id:3698636376 --> @techrome commented on GitHub (Dec 30, 2025): **Update: don't use this workaround, use the hooks instead in my post below** As a workaround I'm currently using a postgres db trigger. My use case was that I created 2 additional fields "pendingEmail" (string) and "pendingEmailSetAt" (date) for the user table. That was done because I allow sign up without requiring email and put a dummy unique mail inside the email column just to keep better-auth validation happy. Then if the user supplied an email, I set it to the pendingEmail column and send the mail. After that, when the user verifies it, the pendingEmail gets moved into email column. But the issue was that I needed to clear pendingEmail after that and that's why I needed the hook. So the current solution for me is to create a trigger like this: ``` CREATE OR REPLACE FUNCTION clear_pending_email_on_verified() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW."email_verified" = TRUE AND NEW."pending_email" IS NOT NULL AND NEW."pending_email" = NEW."email" THEN NEW."pending_email" := NULL; NEW."pending_email_set_at" := NULL; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trigger_clear_pending_email_on_verified ON "user"; CREATE TRIGGER trigger_clear_pending_email_on_verified BEFORE UPDATE OF "email", "email_verified" ON "user" FOR EACH ROW EXECUTE FUNCTION clear_pending_email_on_verified(); ``` This works fine for now but since I'm new to this I would like to hear any feedback whether I'm doing something stupid and maybe there's an already established way to handle such scenario.
Author
Owner

@techrome commented on GitHub (Jan 2, 2026):

Classic case of me not reading the full docs. There are already all the needed hooks on create/update/delete that are fired when the table changes occur. I don't need a trigger for this. https://www.better-auth.com/docs/concepts/database#database-hooks
I can just use databaseHooks instead:

databaseHooks: {
    user: {
      update: {
        async before(user, context) {
          let updatedUser = user;

          if (
            context?.path === "/verify-email" &&
            user.emailVerified &&
            user.email
          ) {
            updatedUser = {
              ...updatedUser,
              pendingEmail: null,
              pendingEmailSetAt: null,
            };
          }

          return { data: updatedUser };
        },
      },
    }, 
<!-- gh-comment-id:3704643957 --> @techrome commented on GitHub (Jan 2, 2026): Classic case of me not reading the full docs. There are already all the needed hooks on create/update/delete that are fired when the table changes occur. I don't need a trigger for this. https://www.better-auth.com/docs/concepts/database#database-hooks I can just use databaseHooks instead: ``` databaseHooks: { user: { update: { async before(user, context) { let updatedUser = user; if ( context?.path === "/verify-email" && user.emailVerified && user.email ) { updatedUser = { ...updatedUser, pendingEmail: null, pendingEmailSetAt: null, }; } return { data: updatedUser }; }, }, }, ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19349