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 16, 2026NextJSBetter Auth UI

Configure Password Reset in BTST Better Auth UI

Connect recovery email delivery, reset-page paths, token validation, and session revocation in an existing Next.js auth integration.

Configure Password Reset in BTST Better Auth UI

Password recovery connects the sign-in page, an email-delivery callback, and a server-validated reset token. BTST supplies the forms; your Better Auth server still needs to issue the link and deliver it.

This guide extends an existing @btst/better-auth-ui@2.0.0 integration with Better Auth 1.6.16. The email example uses a Next.js App Router handler. Start with the auth integration guide if the server, client, and provider are not connected yet.

Configure recovery on the server#

Add the recovery options to your existing emailAndPassword configuration. Preserve its registration, verification, password-policy, and other settings:

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
import type { BetterAuthOptions } from "better-auth";
import { after } from "next/server";
import { sendEmail } from "@/lib/email";

export const passwordRecovery = {
  resetPasswordTokenExpiresIn: 15 * 60,
  revokeSessionsOnPasswordReset: true,
  sendResetPassword: async ({ user, url }) => {
    after(async () => {
      await sendEmail({
        to: user.email,
        subject: "Reset your password",
        text: `Choose a new password using this link: ${url}`,
      });
    });
  },
} satisfies Partial<NonNullable<BetterAuthOptions["emailAndPassword"]>>;

Spread passwordRecovery into the existing emailAndPassword object, which must have enabled: true. This is an addition to a working configuration, not a replacement for your database, secret, plugins, or auth handler.

sendEmail is your server-only delivery function. It must reject when the provider reports a failure, including providers that return an error object instead of throwing. Monitor delivery failures without logging the message body, token, or full URL.

The callback sends Better Auth's supplied URL unchanged. It first reaches the auth API, which checks the token and redirects to the reset form. Substituting the form URL directly loses that step. The plain-text message avoids introducing HTML interpolation.

The Next.js after API lets the response finish before delivery runs. Call this configuration through a Next.js request handler; a standalone script has no matching request lifecycle. Delivery remains bounded by the deployment's function duration, and after is not a durable retry queue. For another framework, use its supported background-task mechanism or your existing durable delivery system. Avoid an untracked promise that may be terminated after the response.

Check the UI paths and password policy#

Merge this credentials configuration into the existing overrides.auth object:

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

export const recoveryFields = {
  credentials: {
    forgotPassword: true,
    confirmPassword: true,
  },
} satisfies Partial<AuthPluginOverrides>;

Preserve any existing credentials.passwordValidation, username, and remember-me settings when merging. UI validation should agree with the server's password policy; the server remains authoritative.

With the default auth view paths and a /p site mount, the request form is /p/auth/forgot-password and the new-password form is /p/auth/reset-password. A /pages site mount changes those prefixes. Neither path is the /api/auth handler.

The released forgot-password form calls requestPasswordReset and builds the destination from the configured site and auth paths. The reset form reads the token from the query string and passes it with the new password to resetPassword. Successful completion returns to sign-in; it does not automatically authenticate the browser.

If the link reaches the wrong host or returns a 404, compare the configured public site origin, mount, auth API origin, and rendered form path. Keep origin checks enabled. Do not fix a bad destination by allowing arbitrary redirects.

Exercise the recovery boundary#

Use controlled accounts to check the complete flow:

CaseExpected behavior
Known emailA delivered link reaches the intended reset form
Unknown emailThe public response does not disclose whether an account exists
Expired or already redeemed tokenThe server refuses another reset
Password outside server policyThe server rejects it even if UI validation is bypassed
Successful resetNew password works; old password fails
Existing sessionsThey are revoked under the explicit policy above

The reset-session policy differs from changing a password while signed in. See the session management guide for cache behavior and a two-browser verification procedure. Keep token query parameters out of analytics and error reports, including the navigation back to sign-in.

The Better Auth recovery documentation describes the server callback and client calls. Use the BTST auth documentation for provider wiring and the customization guide for page copy. Verify actual inbox delivery in your deployment before relying on recovery.

In This Post

Configure recovery on the serverCheck the UI paths and password policyExercise the recovery boundary