JWT expired
A request carried a sign-in token past its expiry time, usually because code saved the token once instead of using the refreshed one.
Also appears as
- {"code":"PGRST303","details":null,"hint":null,"message":"JWT expired"}
- {"code":"PGRST301","details":null,"hint":null,"message":"JWT expired"}
- TokenExpiredError: jwt expired
- JWTExpired: "exp" claim timestamp check failed
Prompt to fix it
Requests start failing with "JWT expired" after the app has been open for a while. Find every place that reads, stores, or sends the Supabase access token. Replace saved copies with a fresh token from supabase.auth.getSession() right before each request, use one shared Supabase client with auto refresh on, and if this is Next.js, add the @supabase/ssr session refresh middleware. Retry once after refreshing if a request still gets a 401. Don’t raise the JWT expiry time to hide the problem.
Paste into Layerwise or any AI coding tool.
What it means
A JWT (JSON Web Token) is the signed pass that proves who a request comes from. Supabase gives each signed-in user an access token that expires after one hour by default, plus a refresh token. supabase-js uses the refresh token to get a new access token shortly before the old one runs out.
So this error almost always means some code sent an old token. It read session.access_token once, kept it in state, localStorage, or a custom Authorization header, and went on sending it after the client had already swapped in a new one. That explains the usual pattern: everything works for an hour, then requests start failing.
Server-rendered apps have their own version. In Next.js, server components can’t write cookies, so the session cookie only gets refreshed if middleware does it on each request (proxy.ts in Next.js 16). Supabase’s @supabase/ssr guide includes that file; without it, server code keeps reading an expired token.
Common causes
- The code saves
session.access_tokenonce and keeps sending it to an API or edge function. - A Supabase client is created with a fixed
Authorizationheader, so it never picks up refreshed tokens. - A Next.js app reads the session on the server, and no middleware refreshes the cookie.
- Your own backend checks tokens with a library like
jsonwebtoken, and the frontend never fetches a new token.
How to fix it
- Search the code for
access_tokenand remove saved copies. - Right before each call that needs the token, get it from
supabase.auth.getSession(), which refreshes an expired session first. - Use one shared Supabase client with the default
autoRefreshToken: true. - In Next.js, add the session-refresh middleware from Supabase’s
@supabase/ssrguide, and create server clients withcreateServerClient. - If a request still returns
401with this message, refresh the session once and retry. - Treat a longer JWT expiry in Supabase’s settings as a last resort: it only delays the error, and a stolen token keeps working longer.
Example fix
async function callApi(path: string, body: unknown) {
// getSession() refreshes the token first if it has expired.
const { data } = await supabase.auth.getSession();
return fetch(path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${data.session?.access_token}`
},
body: JSON.stringify(body)
});
}Related errors
All errors401 Unauthorized
The server couldn’t confirm who was calling: the request had no credentials, or its API key, session, or token was wrong or expired.
AuthSessionMissingError: Auth session missing
A Supabase auth call needed a signed-in user, but the client making it had no session, often because it runs on the server without cookies.
New row violates row-level security policy
Postgres refused to save a row because no row-level security policy on that table lets the current user write it.
Error 400: redirect_uri_mismatch
Google stopped the sign-in because the return address your app sent isn’t on the OAuth client’s list of approved redirect URIs.
Build with an agent that checks its work
The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.