My Supabase password reset link opens the home page, not the reset page. Why?

Checked on

Supabase sends people to your Site URL (usually your home page) when the reset request has no redirectTo, or when the redirectTo address is not on your Redirect URLs list. Add the exact reset page address, for example https://yourapp.com/update-password, under Supabase → Authentication → URL Configuration → Redirect URLs, and pass that same address as redirectTo in resetPasswordForEmail. If the link does reach your app but then jumps to the home page, your app is doing it: the reset link signs the user in, and a “signed in, go home” rule runs before the reset form shows. Show the reset form when the PASSWORD_RECOVERY event arrives, and let that page through your sign-in redirects.

Symptoms

  • A user clicks “Forgot password?”, gets the email, clicks the link, and lands on your home page or dashboard. No “choose a new password” form.
  • The link in the email ends with redirect_to=https://yourapp.com (no /update-password or /reset-password on the end).
  • It works on localhost or in the Lovable, Bolt or Replit preview, but not on your live domain.
  • The user is now signed in, but never set a new password.

Why it happens

The reset goes: your app → Supabase sends the email → user clicks → Supabase checks the link → Supabase sends the user back to your app → your app shows the reset form.

There are four common places it goes wrong:

  1. No redirectTo in the code. Supabase's docs say the Site URL “defines the default redirect URL when no redirectTo is specified”, and that this setting “is critical for email confirmations and password resets”. So with no redirectTo, the link always opens your Site URL, which is your home page.
  2. redirectTo is not on the Redirect URLs list. The redirectTo address “should match the Redirect URLs list”. If it doesn't, people are sent to the Site URL instead. Common mismatches: only https://yourapp.com is listed but you ask for https://yourapp.com/update-password; www. is missing; http instead of https; the address has no https:// at all; or the list still only has the preview or localhost address. A single * does not cover deeper paths: https://yourapp.com/* matches /update-password but not /auth/update-password.
  3. The email template ignores redirectTo. If you changed the “Reset Password” email template and the link is built from {{ .SiteURL }} with nothing after it, every link goes to the home page. Supabase's docs say that when you use redirectTo you may need to use {{ .RedirectTo }} in the template instead.
  4. Your app sends signed-in users to the home page. When the reset link works, the user arrives already signed in (that is how they're allowed to set a new password). Many AI-built apps have a rule like “if signed in, go to the dashboard” or “on SIGNED_IN, go home”. That rule runs first and the reset page never shows. Supabase sends a PASSWORD_RECOVERY event for exactly this case, and its docs say to “show screen to update user's password” when it arrives.

Step-by-step fix

1. Look at the link in the email

Send yourself a reset email from the live site. Don't click the link. Copy it (right-click, “Copy link address”) and paste it somewhere you can read it. Look for redirect_to=.

  • redirect_to=https://yourapp.com or your home page: go to steps 2 and 3.
  • redirect_to=https://yourapp.com/update-password (the right page) but you still land on home: go to step 5.
  • redirect_to=http://localhost:3000: your Site URL is still localhost. See Why does my Supabase email link send people to localhost:3000?

2. Set the Site URL and add the reset page to Redirect URLs

In Supabase → Authentication → URL Configuration:

  • Site URL: your live address, for example https://yourapp.com.
  • Redirect URLs: add the exact reset page, for example https://yourapp.com/update-password. If people also use www., add https://www.yourapp.com/update-password too. Keep your preview address on the list if you test there.

Supabase recommends the exact path for production. https://yourapp.com/** also works, but it allows every page on your site.

3. Pass redirectTo in the reset request

Find the code that sends the reset email. It should look like this:

await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: `${window.location.origin}/update-password`,
})

Use window.location.origin (or your live address) so it is correct on every domain. Don't hardcode a preview or localhost address. The value must start with https://.

4. Check the email template (only if you changed it)

In Supabase → Authentication → Emails → Reset Password, look at the link. If it is {{ .SiteURL }} on its own, change it to use {{ .RedirectTo }}, or go back to the default template, which already uses the right link. If your app uses the server-side (PKCE) setup, Supabase's template is:

<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/update-password">Reset password</a>

Here the next= part decides the page, and your /auth/confirm route must send the user to next after verifyOtp succeeds.

5. Make the reset page show before any “go home” rule

On the reset page (or once in your app's sign-in setup), listen for the recovery event and show the form:

supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'PASSWORD_RECOVERY') {
    // show the "choose a new password" form, don't redirect
  }
})

Then check every place that redirects signed-in users (route guards, “if session, navigate to /dashboard”, a SIGNED_IN handler). Add the reset page as an exception, or check for PASSWORD_RECOVERY first. When the user submits the new password, call:

await supabase.auth.updateUser({ password: newPassword })

and only then send them to the home page or dashboard.

If your app uses the PKCE flow, the user arrives with ?code=... in the address. That code is swapped for a session with exchangeCodeForSession. Supabase says it is valid for 5 minutes, works once, and must be used in the same browser that asked for the reset. A user who asks on their laptop and clicks the link on their phone will get an error, not the reset page.

6. Test in a private window

Open your live site in a private window. Click “Forgot password?”, request the email, click the link, and check that you see the “choose a new password” form, can save it, and can log in with the new password. Supabase's built-in email service only sends 2 emails an hour, so if the email doesn't arrive, wait or set up your own SMTP.

Fix prompt for your AI builder

Paste this into Lovable, Bolt, Cursor, Replit, v0 or whichever tool you build with. Replace the parts in brackets first.

Fix prompt
Our password reset link opens the home page instead of the reset page.
Live site: [https://yourapp.com]. Reset page should be: [/update-password].
The link in the reset email shows redirect_to=[paste what you see].
We use Supabase Auth with email and password.

Do only this:
1. Find the code that calls supabase.auth.resetPasswordForEmail. Make sure it passes
   redirectTo: `${window.location.origin}/update-password` (no hardcoded localhost or preview URL).
2. Make sure a public /update-password page exists. On that page, listen to
   supabase.auth.onAuthStateChange and show the "new password" form when the event is
   PASSWORD_RECOVERY. Save it with supabase.auth.updateUser({ password }).
3. Find every redirect that sends signed-in users to the home page or dashboard
   (route guards, SIGNED_IN handlers). Let /update-password through, so the form shows first.
4. If we use the PKCE flow (?code= in the URL or an /auth/confirm route), make sure the code
   is exchanged once and the user is then sent to /update-password.
5. Tell me exactly what to put in Supabase > Authentication > URL Configuration:
   the Site URL and the Redirect URLs for this domain (with and without www if both are live).
Don't change the login page design, sign-up, or the database.

How to find out sooner next time

A broken reset is easy to miss. Users who can't reset their password usually just leave. Test it after any change to your domain, your Supabase URL settings, your email template, or your app's log-in code. AI edits to the log-in area often touch the redirect rules too.

Heedza's log-in check opens your live log-in page on a schedule. If the page links to a “Forgot password?” page, it checks that page's email field shows too, without sending it. If either page stops opening or loses its form, it sends you a screenshot and a fix prompt. Heedza never signs in, never submits the reset form and never opens reset emails, so it can't see where the reset link lands. Do the private-window test above yourself after changes.

Let Heedza check this for you. Heedza’s log-in check keeps opening your live log-in page and tells you if it stops opening or loses its form. It never logs in or fills in forms, so it can’t see errors that only show after that.

Made with Lovable, Bolt, Replit, Cursor or v0? Free for 1 site: start free or see prices.

More questions

Why am I signed in after clicking the reset link?

That is how Supabase lets the user set a new password. Show the reset form on the PASSWORD_RECOVERY event before any rule that sends signed-in users to the home page.

Does `https://yourapp.com/*` cover every reset page?

No. A single * stops at /, so it matches /update-password but not /auth/update-password. Add the exact path, or use **.

The link works on my laptop but not on my phone. Why?

With the PKCE flow, the reset code must be used in the same browser that asked for the reset, within 5 minutes and only once.

Where this comes from