BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 18, 2026ReactBetter Auth UI

Change Account Email with BTST Better Auth UI

Configure current-address approval, new-address verification, and account settings without mistaking a successful request for a completed change.

Change Account Email with BTST Better Auth UI

Changing an account's email affects its sign-in identity and recovery address. BTST's account settings provide the form; Better Auth decides when the new address replaces the old one. Configure and test that server policy before exposing the control.

This guide targets @btst/better-auth-ui@2.0.1 with @btst/stack@3.1.2 and better-auth@1.6.16. It extends an existing BTST auth integration. The example requires approval from a verified current address followed by verification of the new address.

Configure both email messages#

Merge the following options into your existing Better Auth server configuration. Preserve any other properties in user and emailVerification when merging; a shallow spread of a whole replacement object can discard them.

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
import type { BetterAuthOptions } from "better-auth";
import { after } from "next/server";
import { sendEmail } from "@/lib/email";

export const emailChangePolicy = {
  user: {
    changeEmail: {
      enabled: true,
      sendChangeEmailConfirmation: async ({ user, newEmail, url }) => {
        after(async () => {
          await sendEmail({
            to: user.email,
            subject: "Approve your email change",
            text: `Approve changing your address to ${newEmail}: ${url}`,
          });
        });
      },
    },
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      after(async () => {
        await sendEmail({
          to: user.email,
          subject: "Verify your email address",
          text: `Verify this address: ${url}`,
        });
      });
    },
  },
} satisfies BetterAuthOptions;

sendEmail represents your existing server-only delivery function. This Next.js example uses after within the auth request to keep delivery attached to the request lifecycle. On another framework or host, use its supported background-work mechanism. Observe delivery failures without recording verification links or tokens.

The pinned change-email handler sends the first message to the current address only when that address is verified and the confirmation callback is configured. Without that callback, the ordinary flow verifies the new address directly. The upstream account documentation describes these choices.

For a currently unverified account, this configuration sends verification to the new address without treating the old address as proven. It leaves updateEmailWithoutVerification disabled. Do not add that option just to make the form appear to save immediately.

Keep the account form aligned with the server#

Mount the account plugin alongside the auth plugin. With the usual /p mount, account settings are at /p/account/settings. Merge this fragment into your existing overrides.auth:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
import type { AuthPluginOverrides } from "@btst/better-auth-ui/client";

export const emailChangeUI = {
  changeEmail: true,
  emailVerification: true,
} satisfies Partial<AuthPluginOverrides>;

changeEmail controls the account form. emailVerification makes the account settings able to show its resend-verification control. Neither enables the server endpoint or delivers a message. The released card calls authClient.changeEmail with the new address and the current pathname as its callback.

This guide uses verification links. If the same application also uses the email-code verification flow, test how its server plugin overrides and account resend control interact with email changes. A working registration code flow alone does not verify this link-based address-change sequence.

Verify the identity transition, not just the toast#

For an account whose current address is verified:

  1. Submit a different, controlled address from account settings. Confirm the stored email remains unchanged.
  2. Open the approval message delivered to the current address. Confirm a second message goes to the new address and the stored email is still unchanged.
  3. Follow the second message. Confirm the server now returns the new verified address and the account retains the same user ID.
  4. Refresh the session and test sign-in and recovery using the new address. Check any application cache keyed by email.

Repeat with an unverified account, an expired link, and an address already belonging to another account. The pinned endpoint can return a generic successful response for an already-used address without changing ownership. Avoid converting that response into an email-availability indicator.

The companion's success wording depends partly on the session's previous verification state. In particular, an unverified account can display an updated message while the server is still waiting for new-address verification. Inspect the resulting user record and delivery sequence when testing; a successful request or toast is not proof of a completed change.

Application records should refer to the stable user ID where possible. Updating an email is not a request to create a second user, transfer an organization, or merge another account. If your application copies email into billing or notification systems, define when those copies update and how failed synchronization is retried.

Keep the generated verification URL intact, preserve trusted-origin checks, and never accept arbitrary post-verification redirects from untrusted input. For an account that has lost access to its verified current address, use your deliberate recovery process rather than silently bypassing the approval requirement.

The snippets were type-checked, and the pinned handler was tested with an in-memory adapter and stubbed mail delivery. Those checks cover the two-message transition and rejected or incomplete cases; they do not verify real inbox delivery, an OAuth provider's profile updates, or a full application UI flow. See the BTST auth documentation for provider wiring and the password-recovery guide for the separate recovery path.

In This Post

Configure both email messagesKeep the account form aligned with the serverVerify the identity transition, not just the toast