Start watching free

Error library · An edge function fails

Supabase “Edge Function returned a non-2xx status code”

A button that calls your backend (send an email, take a payment, ask the AI) does nothing, or shows “Edge Function returned a non-2xx status code”. Often it worked in the preview.

Why it happens

Your app called a Supabase edge function, a small piece of backend code, and the function answered with an error. The Supabase client in your app reports only that the answer was not a success. The function’s own message is left out, which is why the error says so little.

The real reason is in the function’s logs. The usual ones:

  • A missing secret. The function reads a key for Stripe, Resend or OpenAI that was never added to the project’s secrets, and crashes on it.
  • An input it didn’t expect. The code throws on an empty field or a missing record.
  • Refused before it ran. A 401 means the request arrived without a valid sign-in token, and edge functions require one unless they are set not to.

Check it in a minute

  1. On the live app, open your browser’s developer tools (right-click, Inspect), go to the Network tab and press the button again.
  2. Click the red request to functions/v1/…. Its status says who refused: 401 is the sign-in token, 500 is the function’s own code. Its Response tab often has the real message.
  3. In Supabase, open Edge Functions, choose the function and open its logs. The error at the moment you clicked is the cause.

How to fix it

Give this to the AI that builds your app: Lovable, Bolt, Cursor, Claude Code or any other.

My app calls a Supabase edge function and gets "Edge Function returned a non-2xx status code".
The function: [FUNCTION NAME]
The status code: [e.g. 500 or 401]
What its log says: [PASTE THE ERROR FROM THE FUNCTION'S LOGS, if you have it]

1. Read the function's code and find what can make it fail with that status. First check that every secret it reads (Deno.env.get) exists in the project's secrets, and tell me which are missing. Never put a secret in the code.
2. Wrap the handler in try/catch. Log the real error with console.error, and answer with a JSON body { "error": "..." } and a fitting status: 400 for bad input, 401 for no sign-in, 500 for anything else. Every response, errors included, must carry the function's CORS headers.
3. Where the app calls supabase.functions.invoke, read the error's response body (error.context in supabase-js) and show the user a plain message instead of failing silently.

Tell me which secrets I need to add and where. After the fix, the button must work on the live app, and a failure must say why in the function's logs.

What Vigilia sees of this

  • The failed request: a call to your function that comes back 401, 403 or 5xx is raised from the first visitor, with the function’s address and the status. A 404 waits for three visitors.
  • The error in the page: if your app doesn’t catch it, the “non-2xx” error arrives too, with what the visitor did just before.
  • The real reason (Pro): with server errors connected, the function’s own crash arrives with its message, and the failed request in the browser says it was caused on your server.

What it can’t see: A function that answers “200 OK” with an error inside the body looks like a success to the browser, so nothing is raised. Step 2 of the prompt fixes that as well.

Know before your users tell you

Vigilia watches your live app, explains what breaks in plain language, hands your AI the fix, and confirms it held with real visitors.

More from the error library