Supabase Edge Function CORS error: Response to preflight request doesn't pass access control check
Calling a Supabase Edge Function from the browser fails with a CORS/preflight error? Handle OPTIONS and return corsHeaders on every response.
Reviewed October 14, 2026
What it means in plain English
Before calling your Edge Function, the browser sends a quick "am I allowed?" (OPTIONS) request. If the function doesn't answer it with the right headers, the real request is blocked.
Likely causes
- The function doesn't handle the
OPTIONSmethod. - CORS headers are returned on success but not on error responses.
- The allowed-headers list is missing headers the Supabase client sends (
authorization,apikey,x-client-info,content-type).
How to fix it, step by step
- Easiest: wrap the function with
withSupabasefromnpm:@supabase/server@^1, which handles CORS and preflight automatically. - Or handle it manually (supabase-js v2.95.0+):
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
export default {
fetch: async (req) => {
if (req.method === 'OPTIONS') return Response.json({ ok: true }, { headers: corsHeaders })
try {
const body = await req.json()
return Response.json({ ok: true, body }, { headers: corsHeaders })
} catch (e) {
return Response.json({ error: String(e) }, { status: 400, headers: corsHeaders })
}
},
}
- Older supabase-js (before v2.95.0): create
_shared/cors.tswithAccess-Control-Allow-Origin,Access-Control-Allow-Headers: authorization, x-client-info, apikey, content-type, x-retry-count, traceparent, tracestate, baggageand allowed methods, and import it. - Redeploy the function (
supabase functions deploy <name>, or ask Lovable to redeploy it).
How to check it worked
- Call the function from the live site; the Network tab should show the OPTIONS request returning 200 followed by the real request.
- Errors from the function should now show their real message instead of CORS.
Sources
Related fixes
Can't fix it? Get help.
If the steps didn't clear the error, send what the screen says. A person can take a look. This is a request for a paid fix, not an instant chat.