Supabase Edge Function CORS Error? How to Fix It (2026)
Your Lovable, Bolt, v0 or Cursor app works until it calls a Supabase Edge Function. Then the console fills with red: Access to fetch at ... has been blocked by CORS policy: Response to preflight request doesn't pass access control check, or No 'Access-Control-Allow-Origin' header is present on the requested resource. You searched for the Supabase Edge Function CORS error because the button does nothing and the function logs look fine.
Good news: this is one of the most fixable errors in a vibe-coded app. The browser is asking your function a question before every call (the preflight), and your function is not answering it with the right headers. Sometimes the real problem is a 401, a crash or a missing secret that only looks like CORS. This guide covers all of it, with code you can paste. If the app just moved off the builder, keep our Lovable app broken after export guide open too.
Quick answer: In your Edge Function, return early on OPTIONS with corsHeaders imported from npm:@supabase/supabase-js@^2/cors, and add the same headers to every response, including errors. Read secrets and parse JSON after the preflight check. Redeploy with supabase functions deploy, then call it with supabase.functions.invoke.
| What you see | Most likely cause | Fix |
|---|---|---|
Preflight doesn't pass access control check | No OPTIONS branch, or it returns non-2xx | Return new Response('ok', { headers: corsHeaders }) first |
No 'Access-Control-Allow-Origin' header | Success or error response sent without CORS headers | Spread corsHeaders into every response, including catch |
Request header field x-client-info is not allowed | Hand-written allow-list missing a header supabase-js sends | Import corsHeaders from the SDK, or add the header |
| CORS error only when signed out or after a while | 401 from the verify_jwt check (no or expired JWT) | Call with functions.invoke and a live session |
| CORS error right after you added a secret or import | Function crashes (500 / 503) before headers are set | Check Logs, set the secret, wrap the handler in try/catch |
| Works on localhost, fails on your domain or Vercel preview | Custom origin allow-list misses that origin | Add the exact origin, or use the SDK wildcard default |
| Works in curl, fails only in the browser | curl does not enforce CORS | Test the preflight with curl |
