FunctionsHttpError: Edge Function returned a non-2xx status code

Supabase · updated 2026-11-10

Returned by supabase.functions.invoke() in supabase-js, in the browser or in Node, when the Edge Function answered with a 4xx or 5xx status. It is returned, not thrown: data is null, and error.context is the function's Response, which holds the status and the message the function sent. The generic text is all you see until you read that Response.

FunctionsHttpError: Edge Function returned a non-2xx status code
    at FunctionsClient.<anonymous> (node_modules/@supabase/functions-js/dist/main/FunctionsClient.js:274:27)
    at Generator.next (<anonymous>)
    at fulfilled (node_modules/tslib/tslib.js:167:62)

Quick fix

Read the function's own answer before guessing: check error instanceof FunctionsHttpError, then error.context.status and await error.context.text() (or .json() if the function returns JSON). A 400 or 422 means the function rejected the input you sent; a 401 or 403 means it rejected the caller; a 500 means the function itself crashed, and the stack trace is in the function's logs in the Supabase dashboard.

  1. Check the class: FunctionsHttpError means the function ran and answered with an error status. FunctionsRelayError and FunctionsFetchError mean the request never got a normal answer (network, relay or wrong URL).
  2. Log error.context.status and the body from error.context.text(). That body is whatever your function returned, usually the real reason.
  3. 4xx: fix the request (missing body field, wrong method, missing or expired session) or the function check that rejects it.
  4. 5xx: open the function in the Supabase dashboard and read its logs for the exception and the line that threw.
  5. Works locally, fails deployed: a secret the function reads with Deno.env.get() is in your local .env but not set on the project (supabase secrets set NAME=value).
  6. In the function, catch errors and return a JSON body with a clear message and the right status, so the client can show it.
typescript
import { FunctionsHttpError } from '@supabase/supabase-js';

const { data, error } = await supabase.functions.invoke('checkout', { body: { cartId } });
if (error instanceof FunctionsHttpError) {
  // The function's own status and message, hidden behind the generic text
  const status = error.context.status;
  const body = await error.context.text();
  console.error(`checkout failed (${status}): ${body}`);
} else if (error) {
  console.error(error.name, error.message); // relay or network problem
}

Why it happens

The function threw

An uncaught exception inside the Edge Function becomes a 500. The client only sees the generic message; the exception and its stack trace are in the function logs.

The function rejected the input

Your own function code returned 400, 404 or 422 because a field was missing or invalid. The body it returned says which, if it returned one.

The function rejected the caller

A 401 or 403 from your function, for example when it checks the signed-in user and the call was made without a session.

Missing secret in production

Deno.env.get() returns undefined for a secret that only exists in your local .env, and the code that uses it throws. Set it on the project with supabase secrets set.

Other fixes

  • FunctionsRelayError ("Relay Error invoking the Edge Function") and FunctionsFetchError ("Failed to send a request to the Edge Function") have different causes: the request never reached a normal answer. Check the function name, the project URL and the network.
  • Return errors as JSON with a message field so error.context.json() gives the client something to show the user.
  • Report FunctionsHttpError yourself (status plus body) to your error tracker: supabase-js does not throw it, so a global error handler never sees it.

Same error, other wording

  • Edge Function returned a non-2xx status code
  • FunctionsRelayError: Relay Error invoking the Edge Function
  • FunctionsFetchError: Failed to send a request to the Edge Function

Let your coding agent catch the next one

supabase-js returns this in the error field instead of throwing it, so unless your code throws it or reports it Runheed doesn't record it. What it does record is the exceptions your running server code throws (route handlers, server rendering, API errors and background jobs) with the stack trace and the breadcrumbs before them, and serves those errors to Claude Code, Cursor or any MCP client.

Setup for Supabase Edge Functions or Node.js and Express, then have your agent fix and resolve it.

Related errors

More in Network and HTTP

References: Supabase docs: Edge Functions error handling (the three error classes, reading error.context), Supabase docs: functions.invoke(), Supabase docs: Edge Function secrets, supabase-js source: FunctionsClient, where the error is created

Spotted a mistake? Email hello@greatwork.company. About Runheed.