---
title: Password reset
description: Build the reset page your app needs, then point Zeitlos at it.
---

Password reset is the one auth flow that needs a page from you. The email has to land
somewhere that collects a new password, and only your app can provide that.

Because of that, **reset is disabled until you configure the page**. Until then,
`forgetPassword` throws `RESET_PASSWORD_NOT_CONFIGURED`.

## How the flow works

1. The user asks to reset their password in your app.
2. You call `forgetPassword({ email })`.
3. Zeitlos emails them a link to **your** reset page, with a token in the query string.
4. Your page collects a new password and calls `resetPassword({ token, newPassword })`.
5. You send them to sign in.

## Setting it up

:::steps

1. **Build the reset page**

   Any route in your app — `/reset-password` is a fine choice. It reads `token` from the
   query string and collects a new password:

   ```tsx title="app/reset-password/page.tsx"
   'use client';

   import { useState } from 'react';
   import { createClient, ZeitlosError } from '@zeitlosapp/sdk';

   const client = createClient();

   export default function ResetPassword() {
     const [error, setError] = useState<string | null>(null);

     async function submit(newPassword: string) {
       const token = new URLSearchParams(location.search).get('token');
       if (!token) return setError('This link is malformed.');

       try {
         await client.auth.resetPassword({ token, newPassword });
         location.href = '/sign-in';
       } catch (err) {
         if (err instanceof ZeitlosError && err.code === 'INVALID_TOKEN') {
           setError('This link has expired or was already used.');
         }
       }
     }

     // …your form, calling submit(newPassword)
   }
   ```

2. **Tell Zeitlos where it is**

   Go to **Authentication → Settings → Password reset** and enter the page's full URL, e.g.
   `https://yourapp.com/reset-password`. The card's badge flips from **Disabled** to
   **Enabled** once saved.

3. **Trigger a reset from your app**

   From your "Forgot password?" screen:

   ```ts
   await client.auth.forgetPassword({ email });
   ```

   This always resolves the same way whether or not the account exists — it does not reveal
   which emails are registered. Show a neutral "if that email exists, we've sent a link"
   message.

:::

> [!TIP]
> You can test the whole flow locally. 
> See [Local development](/authentication/local-development/) for more details.

## Handling an expired link

Reset tokens are single-use and time-limited. A link that has expired or already been used
throws `INVALID_TOKEN` on `resetPassword`. Catch it and offer to send a fresh link rather
than leaving the user on a dead page.

## Where the email comes from

The sender name and reply-to address are under **Authentication → Settings → Email**, along
with a preview of what the message looks like.
