Every time someone tells me they're getting Access-Control-Allow-Origin errors when calling Supabase Edge Functions from their Rork app, the first thing I do is ask them to check the actual error logs. More often than not, CORS isn't the real problem at all.
I've been building mobile apps since 2014, and "CORS error" is one of those terms that gets used for almost any API call failure. When something doesn't work across a network boundary, CORS gets the blame. The Rork + Supabase combination triggers this exact misdiagnosis regularly.
React Native Doesn't Enforce CORS — That's the Key Insight
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. When a page running on https://example.com makes a fetch request to https://api.another.com, the browser blocks it unless the server explicitly allows it. This prevents malicious sites from silently sending requests to other services on your behalf.
React Native — the framework powering Rork apps — runs in a native iOS or Android runtime, not a browser. Which means CORS restrictions simply don't apply when your app is running on a real device or in TestFlight.
// This fetch has no CORS restrictions in React Native
const { data, error } = await supabase.functions.invoke('my-function', {
body: { message: 'hello' }
})So why do developers see CORS-like symptoms? There are three patterns worth knowing.
Pattern 1 — You're Testing in Rork Companion (Browser Preview)
When you preview your Rork app in the PC browser via Companion, that preview runs in a genuine browser environment. And in that environment, CORS absolutely applies.
If the error only shows up in browser preview but not on a real device, the fix is adding CORS headers to your Edge Function:
// supabase/functions/my-function/index.ts
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type',
}
Deno.serve(async (req) => {
// Handle preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const { message } = await req.json()
const result = { response: `Received: ${message}` }
return new Response(JSON.stringify(result), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
} catch (error) {
// Critical: attach corsHeaders to error responses too
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})Two things matter here: responding to OPTIONS preflight requests, and attaching corsHeaders to all responses including errors. If an error response doesn't include CORS headers, the browser can't read the error body and reports a CORS error instead of the actual problem.
Pattern 2 — Your Edge Function Is Crashing with a 500 Error
This is the most frequently missed cause. When a Supabase Edge Function crashes with an unhandled exception, the Deno runtime sends back a generic error response — often without CORS headers attached. In browser preview, this looks exactly like a CORS error.
How to diagnose it:
- Open the Supabase dashboard → Edge Functions → Logs
- Find the log entry at the time of your failed request
- If you see
ErrororUncaught, it's a function bug, not a CORS issue
// Common mistake: no try-catch
Deno.serve(async (req) => {
const { userId } = await req.json() // throws if body is malformed
const user = await getUser(userId) // throws if DB call fails
return new Response(JSON.stringify(user))
})
// Fixed: wrap everything in try-catch, attach corsHeaders everywhere
Deno.serve(async (req) => {
const corsHeaders = { 'Access-Control-Allow-Origin': '*' }
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const body = await req.json()
if (!body.userId) throw new Error('userId is required')
const user = await getUser(body.userId)
return new Response(JSON.stringify(user), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
} catch (error) {
console.error('Function error:', error.message)
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})Pattern 3 — Missing Auth Headers Causing 401s
When calling Edge Functions with a raw fetch instead of supabase.functions.invoke(), missing or incorrect authorization headers result in a 401 response. If that 401 comes back without CORS headers, browser preview displays it as a CORS error.
// ❌ Missing auth headers
const response = await fetch(
'https://xxxxx.supabase.co/functions/v1/my-function',
{
method: 'POST',
body: JSON.stringify({ message: 'hello' }),
}
)
// ✅ supabase-js handles headers automatically
const { data, error } = await supabase.functions.invoke('my-function', {
body: { message: 'hello' },
})Using supabase.functions.invoke() automatically attaches the project's ANON_KEY and the Authorization header. If you need to use raw fetch, include these manually:
const ANON_KEY = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY
const response = await fetch(
'https://xxxxx.supabase.co/functions/v1/my-function',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ANON_KEY}`,
'apikey': ANON_KEY,
},
body: JSON.stringify({ message: 'hello' }),
}
)A Practical Diagnosis Flow
Work through these steps in order and you'll identify the cause in most cases:
Step 1: Does the error appear on a real device (or iOS simulator)?
- Error only in browser preview → Pattern 1: add CORS headers to your Edge Function
- Error on real device too → Not a CORS problem, continue to Step 2
Step 2: Check the Supabase dashboard logs
- Function error logs present → Pattern 2: fix the bug and wrap in try-catch
- No logs at all → Function isn't being called (check URL and auth)
Step 3: Are you using supabase.functions.invoke()?
- Using raw
fetch→ Pattern 3: switch toinvoke()or add headers manually
Supabase Edge Functions are actually quite transparent about what's going wrong — the logs tell the story clearly. When "CORS error" appears, resist the urge to immediately add headers. Start with a real device test and log check instead, and you'll save yourself a lot of unnecessary configuration changes.
For deeper coverage of Edge Functions architecture, see Rork Max × Supabase Edge Functions & Row Level Security — Secure API Design Guide. For general Supabase data loading issues, Rork App: Supabase Data Not Loading — Cause-by-Cause Solutions covers the broader picture.