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

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.
Add the recovery options to your existing emailAndPassword configuration. Preserve its registration, verification, password-policy, and other settings:
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.
Merge this credentials configuration into the existing overrides.auth object:
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.
Use controlled accounts to check the complete flow:
| Case | Expected behavior |
|---|---|
| Known email | A delivered link reaches the intended reset form |
| Unknown email | The public response does not disclose whether an account exists |
| Expired or already redeemed token | The server refuses another reset |
| Password outside server policy | The server rejects it even if UI validation is bypassed |
| Successful reset | New password works; old password fails |
| Existing sessions | They 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.