new row violates row-level security policy (Supabase)
Your insert or update is blocked by RLS. Add a policy for that exact write, without turning RLS off.
The browser's OPTIONS preflight isn't answered. Handle it with withSupabase or the SDK's corsHeaders, and keep secrets server-side.

Browsers send a CORS preflight (OPTIONS) before cross-origin calls. Your Edge Function must answer that preflight and return CORS headers on the real response. Supabase's docs give two supported paths (Supabase: CORS for Edge Functions):
Preferred: wrap the handler with withSupabase, which "handles CORS and preflight (OPTIONS) requests for you":
import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const { name } = await req.json()
return Response.json({ message: `Hello ${name}!` })
}),
}
Manual: if you don't use withSupabase, import corsHeaders from the SDK (v2.95.0+) and return them on both OPTIONS and your responses:
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 { name } = await req.json()
return Response.json({ message: `Hello ${name}!` }, { headers: corsHeaders })
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return Response.json({ error: message }, { status: 400, headers: corsHeaders })
}
},
}
Redeploy the function after the change. Call it with the user's JWT (for example supabase.functions.invoke) when the function uses auth: 'user', not with a secret key in the browser (Supabase: Securing Edge Functions).
A page on https://yourapp.com calling https://YOUR_PROJECT.supabase.co/functions/v1/... is cross-origin. The browser asks permission with OPTIONS first. If the function doesn't handle that request or omits the headers the browser expects, DevTools shows a CORS error and your fetch fails before your JSON runs (Supabase: CORS for Edge Functions).
Importing corsHeaders from the SDK "keeps your allow-list aligned with the headers the client libraries send" (Supabase: CORS for Edge Functions).
service_role key in the browser. Secret keys bypass RLS and must stay server-side (Supabase: API keys).verify_jwt just to clear a 401 during development and leave it off in production. Supabase notes that turning it off removes the platform check (Supabase: Securing Edge Functions). Prefer fixing the client so it sends a real user JWT, or pick the right withSupabase auth mode (user, secret, publishable, or none for signed webhooks).Calling my Supabase Edge Function from the browser fails with a CORS / preflight error.
Update the function to handle OPTIONS and return CORS headers, preferably via withSupabase from @supabase/server, or manually with corsHeaders from @supabase/supabase-js/cors.
Keep secret and service_role keys server-only. Do not disable RLS or leave verify_jwt off in production.
Show the function diff first and wait for my OK.
OPTIONS → 200 with CORS headers, then the real method → 200.Supabase RLS guide · Invalid API key · JWT expired · Stripe webhook signature
Did withSupabase clear it, or did you need the manual corsHeaders path? Tell us which stack you're on (plain Deno, Next proxy, etc.).
This guide is educational and based on the vendor's public docs as of the review date. Platforms change: check the linked sources. Not a security audit. Disclaimer.
From BuiltSafely · builtsafely.com · https://builtsafely.com/fixes/cors-supabase-edge-functions/
The BuiltSafely weekly
Platform changes that affect the security of AI-built apps, checked against official docs. Free. Unsubscribe any time.