useForgotPassword() hook

useForgotPassword() is the headless password-recovery hook behind ForgotPassword. It starts the reset flow for an email address, verifies the OTP that comes back to the user, and then exchanges the reset token for a new session when the password is changed.

Usage

The following example shows a basic usage of useForgotPassword().

export default function ForgotPasswordFlow() {  const { loading, forgotPassword, verifyOtp, resetPassword } = useForgotPassword();  async function run() {    if (loading) {      return;    }    await forgotPassword('jane@example.com');    const verification = await verifyOtp('jane@example.com', '123456');    if ('data' in verification) {      await resetPassword(verification.data.token, 'NewPassword123!');    }  }  return <button onClick={run}>Reset password</button>;}

Return value

The hook returns the following fields and methods.

loading: boolean;
Whether the shared client is still loading.
forgotPassword: (email: string) => Promise<ApiResult<{}>>;
Starts the password-reset flow for an email address.
verifyOtp: (email: string, otp: string) => Promise<ApiResult<{ token: string }>>;
Verifies the OTP from the recovery email and returns the reset token used by the final password-reset step.
token?: string | undefined;
Reset token that must be passed to `resetPassword()`.
resetPassword: (token: string, password: string) => Promise<ApiResult<Session>>;
Resets the password with the verified reset token and returns the resulting session.
id?: number | undefined;
Session identifier.
active_signin?: SignIn | null | undefined;
Active sign-in for the returned session.
id?: string | undefined;
Active sign-in identifier.
active_organization_membership_id?: string | undefined;
Active organization membership for the sign-in.
active_workspace_membership_id?: string | undefined;
Active workspace membership for the sign-in.
expiresAt?: string | undefined;
Sign-in expiry timestamp.
lastActiveAt?: string | undefined;
Last activity timestamp for the sign-in.
signin_attempts?: SigninAttempt[] | undefined | undefined;
Sign-in attempts returned with the session.
[].id?: string | undefined;
Sign-in-attempt identifier.
[].current_step?: 'verify_password' | 'verify_email' | 'verify_email_link' | 'verify_email_otp' | 'verify_phone' | 'verify_phone_otp' | 'verify_second_factor' | 'add_second_factor' | 'complete_profile' | undefined;
Current step for the sign-in attempt.
[].completed?: boolean | undefined;
Whether the sign-in attempt has fully finished.
[].second_method_authentication_required?: boolean | undefined;
Whether the sign-in still requires a second factor.

How it works

The hook itself is linear: request a reset, verify the OTP, then exchange the returned reset token for a session with the new password.
The stock ForgotPassword component adds the screen state and navigation around this hook, but the hook itself does not manage any wizard step state.
A successful resetPassword() call can still return a session with unfinished sign-in attempts. The built-in UI checks for that and sends the user back into the sign-in flow when a second factor is still required.
The hook is email-based. Phone-based recovery and other recovery surfaces are not part of this contract.

When to use it

    Examples

    Start password recovery for an email address

    export default function RequestPasswordReset() {  const { loading, forgotPassword } = useForgotPassword();  async function sendCode() {    if (loading) {      return;    }    await forgotPassword('jane@example.com');  }  return <button onClick={sendCode}>Send recovery code</button>;}

    Verify the OTP and capture the reset token

    export default function VerifyRecoveryCode() {  const { loading, verifyOtp } = useForgotPassword();  async function verify() {    if (loading) {      return;    }    const result = await verifyOtp('jane@example.com', '123456');    if ('data' in result) {      console.log(result.data.token);    }  }  return <button onClick={verify}>Verify recovery code</button>;}

    Reset the password and inspect the returned session

    export default function CompletePasswordReset() {  const { loading, resetPassword } = useForgotPassword();  async function complete() {    if (loading) {      return;    }    const result = await resetPassword('reset-token', 'NewPassword123!');    if ('data' in result && result.data.signin_attempts?.length) {      const attempt = result.data.signin_attempts.at(-1);      console.log(attempt?.current_step);    }  }  return <button onClick={complete}>Set new password</button>;}

    On this page