Skip to content

Supabase Edge Functions errorSupabase Edge Function call blocked by CORS policy

The function works from curl or the dashboard, but the browser says the request was blocked by CORS policy. The browser checks the function’s response headers before your code ever sees the result. Here’s how to make them right, or hand it over at a fixed price.

Short answer

The function has to answer the browser’s OPTIONS preflight request and include CORS headers on every response, errors included. Supabase’s wrapper does this for you, or you can import corsHeaders from supabase-js. If it still fails, read the response status: an error can make the browser report CORS when the real problem is something else.

The usual causes, and the fix

  1. 01

    No answer to the preflight request

    Browsers send an OPTIONS preflight request before cross-origin calls that use headers such as Authorization or a JSON Content-Type. The function must answer it with CORS headers. Supabase’s docs show two ways: the withSupabase wrapper from @supabase/server, which handles CORS and the preflight for you, or corsHeaders imported from supabase-js v2.95.0 and later (earlier versions need a shared cors.ts) plus a check for OPTIONS at the top of the handler.

    import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors';
    
    Deno.serve(async (req) => {
      if (req.method === 'OPTIONS') {
        return new Response('ok', { headers: corsHeaders });
      }
      try {
        const data = { ok: true };
        return Response.json(data, { headers: corsHeaders });
      } catch (error) {
        return Response.json({ error: 'Bad request' }, { status: 400, headers: corsHeaders });
      }
    });
  2. 02

    Headers missing on error responses

    If only the success path adds the headers, any error comes back without them and the browser reports CORS instead of the real error. Supabase notes that an error before the CORS check can make the browser blame CORS. Add the headers to every response, and read the function’s logs in the dashboard to find the actual error. A missing OPTIONS handler can show up there as SyntaxError: Unexpected end of JSON input.

  3. 03

    The platform rejects the request before your code runs

    With JWT verification on (the default), the platform checks the Authorization header first. If it’s missing, malformed or signed with a different key, the platform returns 401 and your code never runs, so none of your headers are added. Call the function with supabase.functions.invoke, which sends the header. Supabase also now recommends turning off the built-in check and verifying the user in your own code.

Checked against the vendor’s docs on : Supabase docs: CORS support for Edge Functions; Supabase docs: Edge Function auth headers; Supabase troubleshooting: Edge Function 500 error responses; Supabase troubleshooting: Edge Function 401 error responses; MDN: Preflight request.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Supabase emails link to localhost · Supabase RLS fixes

Questions

More on this error

Is Access-Control-Allow-Origin: * safe?

It’s common, and Supabase’s own example uses it. Browsers won’t send cookies to a wildcard origin, so it doesn’t expose a cookie session, and a function that checks the caller’s token still rejects requests without one. If the function should only serve your app, set the header to your app’s origin instead.

It works with curl but not in the browser. Why?

curl doesn’t enforce CORS and doesn’t send a preflight request. The browser does both, so a function with no OPTIONS handling or missing headers only fails there.

I added the headers and still see the error. What now?

Open the browser’s network tab and look at the failed request’s status. A 401 points to the JWT check, a 500 points to an error in the function (its logs show which), and an OPTIONS request with no 2xx response points to a missing preflight handler.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; we reply by email, usually within one business day.