# Supabase Edge Function blocked by CORS policy: 3 causes

Your contact form's Supabase Edge Function is “blocked by CORS policy”? Three different Chrome messages, three causes, and the function that fixes all of them.

Author: Formgong
Published: 2026-10-09
Language: en
Canonical: https://formgong.com/en/blog/supabase-edge-function-cors/

Read the end of the Chrome message. “Response to preflight request doesn't pass” means the function does not answer OPTIONS. “Request header field authorization is not allowed” means the allowed headers are too narrow. A plain “No 'Access-Control-Allow-Origin' header” usually means the function crashed and returned an error without CORS headers.

## Which message do you see?

We reproduced each case in Chrome on 09.10.2026 with a stand-in function called from a test page. The beginning is always the same, `Access to fetch at '…/functions/v1/…' from origin '…' has been blocked by CORS policy:`; the end tells you the cause.

Three CORS messages from an Edge Function
End of the messageCauseFix

`Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.`The function does not answer the browser's OPTIONS requestReturn the CORS headers for `OPTIONS` first
`Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.`The allowed headers do not include what supabase-js sendsAllow `authorization, x-client-info, apikey, content-type`
`No 'Access-Control-Allow-Origin' header is present on the requested resource.` (no word “preflight”)The preflight passed, but the real response has no CORS headers: usually the function threw an errorAdd the headers to error responses and read the function logs

In every case your code only sees `TypeError: Failed to fetch`. That is why a form that “just shows success” never notices: the request failed before the page could read anything.

## 1. “Response to preflight request doesn't pass”

A contact form posts JSON with an `Authorization` header, so the browser first sends an `OPTIONS` request and asks whether that is allowed. If your function treats `OPTIONS` like a normal call (it tries to read a JSON body, fails, and answers with an error and no headers), the browser stops there. Answer `OPTIONS` at the very top of the function with the CORS headers and nothing else.

## 2. “Request header field … is not allowed”

`supabase.functions.invoke()` sends `authorization`, `apikey` and `x-client-info`, plus `content-type`. If `Access-Control-Allow-Headers` lists only `content-type`, Chrome names a header that is missing (in our test, `authorization`). Use the header list Supabase ships: import `corsHeaders` from `@supabase/supabase-js/cors` (version 2.95.0 or later), or copy the object below on older versions.

```typescript
// supabase/functions/_shared/cors.ts, for supabase-js older than 2.95.0
export const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
  "Access-Control-Allow-Methods": "POST, OPTIONS",
};
```

## 3. The CORS error that is really a crash

This one fools almost everyone. In our test the function answered the preflight correctly, then failed with `500 {"error":"RESEND_API_KEY is not set"}` but without CORS headers on that error. Chrome showed only `No 'Access-Control-Allow-Origin' header is present`, and the page saw `Failed to fetch`. The real reason, a missing secret, never reached the browser.

So when the message has no word “preflight”, stop editing CORS settings and open the function's logs: in Supabase, Edge Functions → your function → Logs (in Lovable, More → Cloud → Edge functions). Then return every response, errors included, with the CORS headers, so next time the browser shows the real error.

## A function that avoids all three

Answer `OPTIONS` first, put the CORS headers on every response, and catch errors so they come back as JSON the page can show. This follows Supabase's CORS guide.

On the page, show success only when the function answered `ok: true`, and show the error otherwise. In our test of 105 AI-built sites, 27 forms showed success and sent nothing ([the study](/en/blog/contact-forms-that-send-nothing/)).

```typescript
// supabase/functions/contact/index.ts
import { corsHeaders } from "npm:@supabase/supabase-js@^2/cors"; // supabase-js 2.95.0 or later

Deno.serve(async (req) => {
  // 1. Answer the browser's preflight.
  if (req.method === "OPTIONS") return Response.json({ ok: true }, { headers: corsHeaders });

  try {
    const { name, email, message } = await req.json();
    // ... store the message or send the email here ...
    return Response.json({ ok: true }, { headers: corsHeaders });
  } catch (err) {
    // 2. Errors need the CORS headers too, or the browser hides them behind a CORS error.
    const text = err instanceof Error ? err.message : String(err);
    return Response.json({ error: text }, { status: 500, headers: corsHeaders });
  }
});
```

## Or post the form somewhere that already allows it

If the function exists only to receive a contact form, you can drop it. A form backend accepts cross-origin posts from any site and keeps no secret in your code. [Formgong](/en/) takes the post at `https://formgong.com/submit` with a public access key, answers with JSON, and sends each message to Telegram and email (300 a month free). The [Resend errors guide](/en/blog/resend-errors/) covers the email side if you keep the function.

## Frequently asked questions

### Why does my Supabase Edge Function fail with CORS only from the browser?

Browsers enforce CORS, curl and Postman do not. The browser sends an OPTIONS preflight first; the function must answer it with Access-Control-Allow-Origin and the allowed headers.

### Which headers does supabase.functions.invoke need?

authorization, x-client-info, apikey and content-type. Import corsHeaders from @supabase/supabase-js/cors (2.95.0 or later) to get the current list.

### The preflight works but I still get a CORS error. Why?

Usually the function threw an error and returned it without CORS headers, so the browser hides it. Check the function logs and add the CORS headers to error responses.

### Does mode: 'no-cors' fix it?

No. It sends the request but makes the response unreadable, so your page cannot tell success from failure. Fix the headers on the function instead.

## Sources and documentation

- [Supabase: CORS support for Edge Functions](https://supabase.com/docs/guides/functions/cors)
- [MDN: Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)
- [MDN: Preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)
- [HTML contact form](https://formgong.com/en/docs/html/)

[Get a form key](https://formgong.com/en/#top)
