Supabase Auth flow_state_not_found or flow_state_expired

Supabase sign-in fails with flow_state_not_found or flow_state_expired? The PKCE login expired or was already used. Start the sign-in again in the same browser.

Reviewed October 25, 2026

What it means in plain English

Supabase's Auth error codes page lists two related codes:

  • flow_state_expired: the PKCE flow state that the request relates to has expired. Ask the user to sign in again.
  • flow_state_not_found: the PKCE flow state no longer exists. Flow states expire after a while and are cleaned up. Retried requests can also cause it, because the earlier request likely used up the flow state. Ask the user to sign in again.

PKCE is the login flow where Supabase sends the user back to your app with ?code=... in the URL, and your app calls exchangeCodeForSession(code). Supabase's PKCE guide says the code is valid for 5 minutes and can be exchanged only once. After that you must restart the sign-in from scratch.

Likely causes

  • The user clicked the email link or finished the OAuth sign-in more than 5 minutes after it started.
  • Your callback page exchanged the code twice, for example a retry, a page reload, or code that runs the exchange more than once. The second exchange fails.
  • The link was opened in a different browser or device. Supabase says the code verifier is stored locally when the flow starts, so the exchange must happen on the same browser and device.
  • Two sign-ins were started in the same browser before either finished, for example signInWithOAuth() in two tabs. Supabase says the later flow overwrites the earlier flow's code verifier, so the first one fails.

How to fix it, step by step

  1. Read error.code in your app. Supabase says to use error.code and error.name, not string matching on the message.
  2. When the code is flow_state_expired or flow_state_not_found, show a message and send the user back to sign in again. Supabase's own guidance for both codes is to ask the user to sign in again.
  3. Make sure your callback route calls exchangeCodeForSession(code) once per sign-in. Do not retry it automatically with the same code.
  4. Tell users to open the email link in the same browser where they asked for it.
  5. If users start sign-in in more than one tab, Supabase has an experimental option, appendPkceFlowIdToRedirects. It adds an sb_flow_id parameter to the redirect so you can pass the right flow to exchangeCodeForSession(code, { flowId }). Supabase says this option is experimental and its API may change without notice.

How to check it worked

  • A fresh sign-in, finished within a few minutes in the same browser, lands the user signed in.
  • Reloading the callback page does not trigger a second code exchange.
  • Your app shows a clear "please sign in again" message instead of a blank error when the code has expired.

Sources

More Supabase auth and email fixes

Keep going on your own.

The guides stay free. A deep leak report is $15. Fix packs are $24, or $49 for all three. There is no done-for-you repair.

See products