◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-05-11Intermediate

That 'CORS Error' in Your Rork + Supabase Edge Function? It's Probably Something Else

Seeing CORS errors when calling Supabase Edge Functions from your Rork app? React Native apps don't enforce CORS — the real cause is usually something else entirely. Here's how to diagnose and fix it.

Supabase37Edge Functions3CORStroubleshooting69React Native239

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:

  1. Open the Supabase dashboard → Edge Functions → Logs
  2. Find the log entry at the time of your failed request
  3. If you see Error or Uncaught, 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 to invoke() 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.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.
⬡ Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.