Calling functions from your app

Call SupaHost Edge Functions with supabase-js functions.invoke, fetch, curl and other clients, pass bodies and headers, call sub-paths and handle errors.

A deployed function is an HTTPS endpoint, so anything that can send a request can call it. supabase-js wraps that in functions.invoke, which adds the right headers for you.

Every function URL has the form:

https://app-one.acme.supahost.space/functions/v1/<function-name>

With supabase-js

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);

const { data, error } = await supabase.functions.invoke("create-invoice", {
  body: { customerId: "c_381", lines: [{ item: "Design work", hours: 6, rate: 90 }] },
});

invoke sends:

  • Authorization: Bearer <token>: the signed-in user's access token if there's a session, otherwise the anon key. That's what the Require a JWT check looks at.
  • apikey with the anon key. SupaHost doesn't need it on /functions/v1/*, but it does no harm.
  • A Content-Type that matches the body.

Bodies, methods and headers

You pass as body Sent as
A plain object or array JSON (application/json)
A string text/plain
FormData multipart/form-data
Blob / File / ArrayBuffer application/octet-stream

The default method is POST. Change it, and add your own headers, with the options object:

const { data, error } = await supabase.functions.invoke("invoices/inv_1042", {
  method: "GET",
  headers: { "x-request-source": "dashboard" },
});

Putting a path after the name (invoices/inv_1042) calls the same function with a longer path, which is how you build small REST APIs inside one function (see Writing functions). Add any custom header to your function's CORS Access-Control-Allow-Headers list.

data is parsed from the response's Content-Type: an object for application/json, a Blob for application/octet-stream, FormData for multipart/form-data, the raw Response for text/event-stream, and text for anything else.

Handling errors

error is one of three classes exported from supabase-js:

Class When
FunctionsHttpError The function (or the router) answered with a non-2xx status. error.context is the Response.
FunctionsRelayError The request failed between the gateway and the function.
FunctionsFetchError The request never got a response (network down, DNS, CORS blocked).
import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";

const { data, error } = await supabase.functions.invoke("create-invoice", { body: draft });

if (error instanceof FunctionsHttpError) {
  const code = error.context.headers.get("sb-error-code"); // set on router errors, e.g. UNAUTHORIZED_INVALID_JWT
  const details = await error.context.json().catch(() => null);
  console.error(`Function failed (${error.context.status})`, code, details);
} else if (error instanceof FunctionsRelayError) {
  console.error("Relay error:", error.message);
} else if (error instanceof FunctionsFetchError) {
  console.error("Network or CORS problem:", error.message);
} else {
  console.log("Invoice created", data);
}

The sb-error-code header tells router errors (NOT_FOUND, UNAUTHORIZED_NO_AUTH_HEADER, UNAUTHORIZED_INVALID_JWT, BOOT_ERROR, WORKER_RESOURCE_LIMIT, IDLE_TIMEOUT, WORKER_ERROR) apart from errors your own code returned. See Logs and debugging for what each means.

With fetch

Use fetch when you want a streamed response or don't use supabase-js:

const { data: { session } } = await supabase.auth.getSession();

const res = await fetch(`${SUPABASE_URL}/functions/v1/export-invoices`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ month: "2026-10" }),
});

if (!res.ok) throw new Error(`export-invoices failed: ${res.status}`);
const csv = await res.text();

From a terminal

curl -i -X POST "https://app-one.acme.supahost.space/functions/v1/create-invoice" \
  -H "Authorization: Bearer $SUPABASE_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"c_381","lines":[{"item":"Audit","hours":2,"rate":120}]}'

-i prints the response headers, including sb-error-code when the router rejects the call.

From your server

Server code can call a function as a user (forward their access token) or as your backend. For backend-only functions, send the service_role key and check the role inside the function (see Authentication and JWT verification):

import { createClient } from "@supabase/supabase-js";

// Server only: never ship the service_role key to a browser.
const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, {
  auth: { persistSession: false },
});

const { data, error } = await admin.functions.invoke("rebuild-reports", { body: { since: "2026-11-01" } });

From another function

Inside a function, create a client with SUPABASE_URL and the key you want to call with, then use functions.invoke the same way. Chained calls each count toward the 150-second response limit of the outer function, so keep them short.

From the database

To call a function when a row changes, use a database webhook or net.http_post from a trigger: see Database webhooks and HTTP requests with pg_net. For a timer, see Running functions on a schedule.

Other client libraries

The Flutter, Swift, Kotlin and Python Supabase clients all have a functions.invoke method that works the same way against your SupaHost project URL. See the JavaScript reference for functions.invoke for every option.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us