"Failed to send a request to the Edge Function" (or "blocked by CORS policy")
One error, one plain-English fix you can paste into your AI tool. For apps whose buttons call a Supabase Edge Function from the browser (checkout, send email, AI reply, form submit). Typical of Lovable, Bolt, Cursor and Replit apps on Supabase. There's also a short section for Lovable's built-in backend (Cloud), which runs edge functions too.
Last checked against official docs: 23 Oct 2026.
Symptom
- You click a button that should do something "on the server" (pay, send an email, ask the AI) and nothing happens, or you get a toast saying
Failed to send a request to the Edge Function. - The browser console shows a red line like
Access to fetch at 'https://<project>.supabase.co/functions/v1/<name>' ... has been blocked by CORS policy. - The function's logs show nothing, or show an error your app never displayed.
Why it happens (plain words)
Your app runs on one web address (your domain). The Edge Function lives on another (<project>.supabase.co). Browsers are strict about this: for security reasons they restrict requests from a page's code to a different address, unless the other side's answer includes the right CORS headers (permission slips that say "this website may read my reply").
The Supabase client sends extra headers (authorization, apikey, x-client-info, and content-type when there's a body). Those aren't on the browser's short list of "simple" headers, so the browser first sends a quick check request, called a preflight, using the OPTIONS method. Supabase's docs say that to invoke edge functions from the browser "you need to handle CORS Preflight requests." If the function doesn't answer that check with the right headers, the browser never sends the real request.
When the request can't get through, the Supabase client gives you only one generic message: Failed to send a request to the Edge Function (FunctionsFetchError, "Function couldn't be reached at all"). Supabase lists the usual causes:
- The function doesn't answer the
OPTIONSpreflight, or its reply is missingAccess-Control-Allow-Origin, or the allowed headers/methods don't match what the app sends. - Error replies without CORS headers. Supabase's example adds the CORS headers to error responses too. If your function returns an error without them, the browser blocks that reply as well, and you can't see what went wrong.
- The function isn't deployed, or the name doesn't match. Supabase warns that "Browsers misreport 404s as generic
CORS errors", so a typo in the function name looks exactly like a CORS problem. There's alsoNOT_FOUND_FUNCTION_BLOB: the function shows asACTIVEin the dashboard, but its deployed code can't be found. - Wrong function URL or network problems.
Good to know: if you instead see Edge Function returned a non-2xx status code (FunctionsHttpError), the request did get through. Your function ran and returned an error, so read its logs (step 5 of the fix).
How to confirm
- Open your live site → DevTools → Network tab. Click the button again.
- Look for requests to
/functions/v1/<function-name>:- A failed OPTIONS request, or a console error mentioning "preflight" or
Access-Control-Allow-Origin→ the function doesn't handle CORS (cause 1). - OPTIONS is fine but the real request (POST) fails → the function probably hit an error and replied without CORS headers (cause 2). Check its logs.
- A failed OPTIONS request, or a console error mentioning "preflight" or
- Check the exact function name your code calls (in
supabase.functions.invoke('...')) against the name in the dashboard. Look for capital letters, dashes and typos (cause 3). - Supabase: open Edge Functions (the docs call it Functions) → [your function]. The Invocations view shows each request and response, and Logs shows deployments and errors. From the function page you can also send a test call. If the test call fails too, the function itself is broken or not deployed.
- Lovable Cloud: go to More → Database → Manage → Edge functions to see each function's status, success rate and failed calls. Click View logs to see the error output. Logs are also under More → Settings → Logs. If the project is paused, its edge functions don't run at all.
Step-by-step fix
- Answer the preflight first. At the very top of the function, before it reads the request body or checks anything, it must reply to
OPTIONSrequests with the CORS headers. (For Lovable, Bolt or Cursor, use the prompt below. You don't need to edit the code yourself.) - Use Supabase's ready-made header list. For
@supabase/supabase-jsv2.95.0 and later, Supabase recommends importingcorsHeadersfrom the SDK, so the list stays in sync with the headers the client sends. (An example based on Supabase's docs is right after these steps.) - Put the CORS headers on every response, including errors and early "return" lines (for example "missing email" or "not logged in"). Otherwise the browser hides the real error from you.
- Fix name or deploy problems. Make sure the name in
supabase.functions.invoke('...')exactly matches the deployed function. Then redeploy the function: from the Supabase dashboard (the function's code tab), withsupabase functions deploy <name>, or by asking your AI tool to redeploy it. Supabase recommends a redeploy if you getNOT_FOUND_FUNCTION_BLOB. - If OPTIONS now works but the call still fails, read the function's logs (Supabase: Edge Functions → [function] → Logs; Lovable Cloud: View logs). One cause to check is a missing secret: Supabase's docs say local and production load secrets differently, "so set them in both". Production secrets go in the dashboard's Edge Function Secrets (or
supabase secrets set). On Lovable Cloud they go in More → Settings → Secrets. Supabase says functions read a new secret immediately, with no redeploy needed. - Lovable Cloud: if the project is paused, resume it. Edge functions don't run while a project is paused.
Based on Supabase's examples (answer OPTIONS first, CORS headers on every response, errors included):
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// ... your function logic ...
return new Response(JSON.stringify({ ok: true }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return new Response(JSON.stringify({ error: message }), {
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
}
})
On an older supabase-js, Supabase's docs show the same idea with a hand-written corsHeaders object in supabase/functions/_shared/cors.ts. Its allowed headers must cover every header your app sends (at least authorization, x-client-info, apikey, content-type).
Copy-paste prompt for your AI tool
My app calls a Supabase Edge Function from the browser and gets "Failed to send a request to the Edge Function" and/or "blocked by CORS policy" in the console. Function name: [NAME]. Exact console error: [PASTE].
1. In that function (and every other edge function my frontend calls), handle the CORS preflight FIRST: if req.method === 'OPTIONS', immediately return a response with the CORS headers, before reading the body or checking anything else.
2. Use corsHeaders imported from 'npm:@supabase/supabase-js@^2/cors' (or, if the project uses supabase-js older than 2.95.0, a shared corsHeaders object that allows authorization, x-client-info, apikey, content-type).
3. Add the CORS headers to EVERY response, including all error responses and early returns, and wrap the logic in try/catch so errors come back as JSON with a clear message and a proper status code.
4. Check that the name used in supabase.functions.invoke matches the deployed function name exactly, then redeploy the function.
5. In the frontend, handle the three error types (FunctionsHttpError, FunctionsRelayError, FunctionsFetchError) and show the user a clear message instead of failing silently.
6. Do NOT turn off JWT verification (verify_jwt) to make an error go away, do NOT move any secret key into frontend code, and do NOT call the external API directly from the browser instead.
Explain what you changed in plain English, and list any secrets I must set in the dashboard myself.
How to verify it's fixed
- On your live site, in a private window, open DevTools → Network and click the button again. The
OPTIONSrequest to/functions/v1/<name>should succeed, followed by your real request. - The console has no
blocked by CORS policyline and the app no longer showsFailed to send a request to the Edge Function. - The feature actually works (payment page opens, email arrives, AI answers). In the function's Invocations/logs you see the call succeed.
- Make the function fail on purpose (for example, submit the form with a required field empty). Your app should show a clear error message, not a CORS error.
Safety notes
- Don't switch off JWT verification to "fix" the error. Supabase's default is
verify_jwt = trueand says to leave it on for user-facing functions. Turning it off to clear a 401 removes the platform's check. - Don't "solve" CORS by calling the paid API (Stripe, OpenAI, Resend…) straight from the browser. That means putting the secret key in frontend code. Secret keys belong in edge function secrets. Lovable's docs: secrets "never reach the browser", while
VITE_values are browser-exposed. See our exposed-keys guide. - CORS is something browsers enforce. It is not a login check, so keep your function's own sign-in and permission checks (Supabase's "Securing Edge Functions" guide).
- If a function uses the admin (secret / service role) key, it bypasses Row Level Security. Supabase warns: filter by the caller's ID, or every user's rows can come back. See our RLS guide.
- Don't put secret values in error messages or logs while debugging. Supabase's own example returns whether a secret is set (
configured: true/false), never the value.
Sources
- Supabase: CORS support for invoking from the browser (preflight, corsHeaders import from v2.95.0, headers the client sends, older-version example), https://supabase.com/docs/guides/functions/cors
- Supabase troubleshooting: Unable to call Edge Function (OPTIONS handling, common CORS causes, Invocations and Logs tools), https://supabase.com/docs/guides/troubleshooting/unable-to-call-edge-function
- Supabase troubleshooting: Edge Function 404 error response (404s misreported as CORS errors, "Failed to send a request to the Edge Function", NOT_FOUND_FUNCTION_BLOB, test call, redeploy), https://supabase.com/docs/guides/troubleshooting/edge-function-404-error-response
- Supabase: Edge Functions error handling (FunctionsHttpError, FunctionsRelayError, FunctionsFetchError), https://supabase.com/docs/guides/functions/error-handling
- supabase-js source: exact error messages ("Failed to send a request to the Edge Function", "Edge Function returned a non-2xx status code"), https://github.com/supabase/supabase-js/blob/master/packages/core/functions-js/src/types.ts
- Supabase: Edge Function environment variables / secrets (set in both local and production, no redeploy needed), https://supabase.com/docs/guides/functions/secrets
- Supabase: Securing Edge Functions (verify_jwt default, service-role warning), https://supabase.com/docs/guides/functions/auth
- MDN: Cross-Origin Resource Sharing (CORS) (same-origin rule, simple vs preflighted requests), https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
- Lovable: Edge functions (monitoring path, View logs, paused projects), https://docs.lovable.dev/features/edge-functions
- Lovable: Logs (More → Settings → Logs), https://docs.lovable.dev/features/logs
- Lovable: Secrets (More → Settings → Secrets, secrets vs VITE_ variables), https://docs.lovable.dev/features/secrets
