CORS (Cross-Origin Resource Sharing) support for Invoking from the browser
To invoke edge functions from the browser, you need to handle CORS Preflight requests.
Automatic CORS handling#
The withSupabase wrapper handles CORS and preflight (OPTIONS) requests for you, so you don't add headers manually:
import { withSupabase } from 'npm:@supabase/server@^1'export default { fetch: withSupabase({ auth: 'user' }, async (req, ctx) => { const { name } = await req.json() return Response.json({ message: `Hello ${name}!` }) }),}Manual CORS handling#
If your function doesn't use withSupabase, add the headers yourself. See the example on GitHub.
For @supabase/supabase-js v2.95.0 and later: Import CORS headers directly from the SDK to ensure they stay synchronized with any new headers added to the client libraries.
Import corsHeaders from npm:@supabase/supabase-js@^2/cors to automatically get all required headers:
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'console.log(`Function "browser-with-cors" up and running!`)export default { fetch: async (req) => { // Handle the CORS preflight request. if (req.method === 'OPTIONS') { return Response.json({ ok: true }, { headers: corsHeaders }) } try { const { name } = await req.json() return Response.json({ message: `Hello ${name}!` }, { headers: corsHeaders }) } catch (error) { const message = error instanceof Error ? error.message : String(error) return Response.json({ error: message }, { status: 400, headers: corsHeaders }) } },}Importing from the SDK keeps your allow-list aligned with the headers the client libraries send: when you upgrade the SDK version in your function and redeploy, newly added headers are picked up with it. As of @supabase/supabase-js v2.112.3 the list includes the trace context headers (traceparent, tracestate, baggage) used by client-side tracing — functions deployed with an older version need a redeploy before browsers can call them with trace propagation enabled.
The allow-list only states what a browser may send — it doesn't change what the SDK sends. Trace context headers are sent exclusively by clients that explicitly opt in to trace propagation (tracePropagation: true, disabled by default). Clients that haven't opted in send exactly the same headers as before.
The full list, and when each header is sent:
| Header | Sent |
|---|---|
authorization | Every request (session token or API key) |
apikey | Every request |
x-client-info | Every request (SDK name and version) |
content-type | Requests with a body |
x-retry-count | Only on automatic retry attempts (postgrest-js retries failed idempotent requests by default) |
traceparent, tracestate, baggage | Only when trace propagation is explicitly enabled — never by default |
For versions before 2.95.0#
If you're using @supabase/supabase-js before v2.95.0, you'll need to hardcode the CORS headers. Add a cors.ts file within a _shared folder. The list must cover every header your calling clients send — include the trace context headers if any client enables tracePropagation:
export const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-retry-count, traceparent, tracestate, baggage', 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',}Then import it in your function:
import { corsHeaders } from '../_shared/cors.ts'// ... rest of your function code