Tutorial

Why am I getting a CORS error?

The browser console shows blocked by CORS policy and your fetch() never gets a body. What that actually means, why it happens, and three ways to fix it.

~7 min read

The error you're staring at

It looks something like this:

Access to fetch at 'https://api.example.com/users' from origin
'https://app.mysite.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

The frustrating bit: your server did respond. If you check the Network tab, you'll often see a 200 with the body you wanted. The browser just refuses to hand the response to your JavaScript.

What CORS actually is (briefly)

CORS — Cross-Origin Resource Sharing — is a security rule the browser enforces, not the server. When JavaScript running on app.mysite.com tries to fetch from api.example.com, the browser checks whether the response is allowed to be shared across origins. The check happens in two ways:

The server-side origin doesn't care about CORS. curl, server-to-server requests, and mobile apps don't trigger it. It is purely a browser-side safety net to stop one site from quietly reading another site on the user's behalf.

Why your server is "refusing"

Almost always one of three things:

  1. The server returns the response, but no Access-Control-Allow-Origin header — the browser blocks the JS from reading it.
  2. The server returns Access-Control-Allow-Origin: https://other.example and your origin doesn't match.
  3. The request was preflighted; the server's OPTIONS response is missing or doesn't include the method/header your fetch is using.

It's worth opening DevTools → Network and looking at both the failed request and the OPTIONS right before it. The headers tell you exactly which check failed.

Three ways to fix it

Option 1 — Configure CORS on the server (the real fix)

If you control the API, this is the right answer. Examples:

Node / Express:

import cors from 'cors';

app.use(cors({
  origin: ['https://app.mysite.com', 'http://localhost:3000'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Go (net/http):

func cors(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "https://app.mysite.com")
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
    if r.Method == http.MethodOptions {
      w.WriteHeader(http.StatusNoContent)
      return
    }
    next.ServeHTTP(w, r)
  })
}

Python / FastAPI:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.mysite.com"],
    allow_methods=["*"],
    allow_headers=["*"],
)

Use specific origins, not *, the moment you start sending credentials or auth headers — browsers refuse the combination.

Option 2 — Proxy through your own backend

If the API is third-party and doesn't return CORS headers (lots of older APIs and partner systems), call it from your own server instead. Your frontend hits app.mysite.com/api/forward, your backend forwards the request to the third party, and you set CORS on your own endpoint however you like.

This is the cleanest answer for production, because you also get a place to put rate limiting, secrets, and observability.

Option 3 — Use a CORS proxy

For development, prototypes, and "we just need to ship this", a CORS proxy in the middle does the same thing without you having to host anything. You replace the URL:

// Before — blocked by CORS
fetch('https://api.example.com/users')

// After — proxied through corsproxy.dev
fetch('https://api.corsproxy.dev/proxy?url=https://api.example.com/users', {
  headers: { 'X-API-Key': 'sk_live_...' }
})

That's what we built corsproxy.dev to do. The hosted version gives you API keys and rate limits so the proxy can't be abused on your behalf; the open-source version is the same Go binary you can run yourself.

Skip the setup

Free tier: 100 requests per day, no credit card. Create a key and you're proxying in under a minute.

Get a free key →

Things that look like CORS but aren't

One sentence summary

CORS errors come from the browser refusing to share an API response across origins because the server didn't say it was allowed. The fix is to either change the server, route the request through your backend, or use a proxy that does the same.