Skip to main content
TF
11 min readArticle

What Is a CORS Error? Here's How to Actually Fix It

TF
ToolsFuel Team
Web development tools & tips
Server rack in a data center with glowing network lights

Photo by Taylor Vick on Unsplash

The Error That Stopped Me Cold on a Friday Afternoon

I had a working React app making API calls to a different domain. Everything was fine in Postman. Everything was fine in curl. Then I opened Chrome and got:

``` Access to fetch at 'https://api.otherdomain.com/data' from origin 'https://myapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ```


The classic. I'd seen this error before, but I'd always fixed it by googling and adding some header without actually understanding what I was doing. This time I decided to figure out what CORS actually is.


Turns out, it's not a server bug. It's not a misconfiguration in most cases. It's a browser security feature working exactly as designed. Which means the "fix" isn't obvious from the error message — and if you don't understand why it exists, you'll keep running into it and keep cargo-culting solutions that half-work.

Why Browsers Block Cross-Origin Requests

CORS stands for Cross-Origin Resource Sharing. Before understanding it, you need to understand the Same-Origin Policy, which is the actual restriction CORS relaxes.

Origins are defined by three things: the protocol (http vs https), the domain, and the port. Two pages have the same origin only when all three match exactly. `https://myapp.com` and `https://api.myapp.com` are different origins — the subdomain counts. `https://myapp.com:3000` and `https://myapp.com` are different origins too — different ports.


Browsers have enforced the
Same-Origin Policy since the late 1990s. The reason is simple: without it, any website could silently make requests to your bank, your email provider, or any other service you're logged into — and those requests would carry your cookies and session tokens. A malicious ad on any random site could drain your bank account in the background.

I'd never thought about that until I read the actual MDN documentation on the
Same-Origin Policy and CORS. It clicked immediately. The browser is your browser — it's protecting you from code running in web pages, not just protecting the server.

CORS doesn't remove this protection. It gives servers a way to explicitly opt in to cross-origin requests from specific sources. The server says, "yes, I trust code running on myapp.com to call my API." The browser checks for that permission before actually sending the full request. If it's not there, blocked.

Simple Requests vs Preflighted Requests

Here's where CORS gets a little weird, because there are two different types of cross-origin requests and they work differently.

Simple requests are ones that meet a strict set of criteria: they're GET, HEAD, or POST methods; they use only a handful of allowed headers (like Content-Type, Accept, Accept-Language); and for POST requests, the Content-Type must be application/x-www-form-urlencoded, multipart/form-data, or text/plain. If all those conditions are met, the browser sends the request directly and checks the response headers to see if the origin is allowed.


Preflighted requests are everything else. Before sending your actual request, the browser automatically fires an HTTP OPTIONS request to the same URL, asking the server: "hey, is this allowed?" The server responds with which origins, methods, and headers it accepts. Then — only if the server gives the green light — the browser sends the real request.


This is why you sometimes see two requests in the Network tab when you're debugging CORS issues. That OPTIONS request you didn't write is the preflight.


A fetch request using `application/json` with a custom Authorization header? That's definitely a preflighted request. Which means your server needs to handle OPTIONS requests and respond with the right headers, or the whole thing fails before your actual code even runs.


I've seen developers spend an hour debugging a CORS issue only to realize their server framework was routing OPTIONS requests to a catch-all 404 handler. The preflight was failing silently and they had no idea.

The Headers That Actually Fix It

Developer reviewing code in a terminal window on a laptop

Photo by Florian Olivo on Unsplash

The core CORS header is `Access-Control-Allow-Origin`. It tells the browser which origins can access the response.

``` Access-Control-Allow-Origin: https://myapp.com ```


Or for development, the wildcard that allows any origin:


``` Access-Control-Allow-Origin: * ```


The wildcard is fine for genuinely public APIs. Don't use it for anything that requires authentication — a wildcard origin doesn't work with `credentials: 'include'` anyway, so it won't let requests with cookies through.


For preflighted requests, you also need:


- `Access-Control-Allow-Methods` — which HTTP methods are allowed - `Access-Control-Allow-Headers` — which request headers are allowed - `Access-Control-Max-Age` — how many seconds the preflight result can be cached


A typical server config for a modern API looks like this:


``` Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400 ```


That last header — `Access-Control-Max-Age` — is worth setting because without it, browsers re-send the OPTIONS preflight before every single request. Set it to 86400 (24 hours) and the browser caches the preflight result, which stops the double-request overhead.


For requests that need to include cookies or HTTP auth (which are called credentialed requests), you need an additional response header:


``` Access-Control-Allow-Credentials: true ```


And your fetch call needs `credentials: 'include'`. And `Access-Control-Allow-Origin` must be a specific origin, not `*`. Get any of those three things wrong and you'll get a different CORS error from the one you started with.


If you're ever unsure what encoding a particular value needs before it goes into a header, ToolsFuel's
URL encoder/decoder can help you check what percent-encoded form a string should take in header values.

Where to Actually Set These Headers

The headers need to go on the server that's handling the API request — not your frontend. I know that sounds obvious, but I've seen developers add CORS headers to their React app's response headers and wonder why nothing changed.

How you add them depends on your stack.


For Node.js/Express, the `cors` npm package handles this in about three lines:


```javascript const cors = require('cors'); app.use(cors({ origin: 'https://myapp.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true })); ```


For Next.js API routes, you can add response headers directly or use a middleware wrapper:


```javascript export default function handler(req, res) { res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { res.status(200).end(); return; } // ... rest of your handler } ```


Note that OPTIONS check at the end. This is easy to forget and it means preflighted requests to your Next.js API route will never complete. You need to return 200 for OPTIONS explicitly.


For Django, the `django-cors-headers` package does the heavy lifting. For Spring Boot, there's `@CrossOrigin` at the controller level or a global `WebMvcConfigurer`. For Nginx, you can set CORS headers in the location block.


One thing I'd strongly recommend: don't set CORS headers on individual endpoints one at a time. Use a middleware or gateway-level config that applies them universally. When you do it piecemeal, you end up with some endpoints working and some not, and debugging which one is missing headers is deeply unpleasant.


And if you're working with
JWT tokens in your Authorization header, remember that any header beyond the standard set triggers a preflight. Your server needs to explicitly list `Authorization` in `Access-Control-Allow-Headers` or those requests will be blocked before they start.

3 Mistakes That Keep Biting Developers

Developer working on code displayed across multiple monitors in dim lighting

Photo by Kevin Ku on Unsplash

I've made all three of these personally.

**Setting CORS headers on the wrong server.** If you're calling `api.otherdomain.com` from `myapp.com`, the CORS headers need to be on `api.otherdomain.com`. If you don't control that server, you can't fix it from your side directly — you'd need a backend proxy that sits on your own domain and forwards the request.


**Using a proxy in development and forgetting to set up CORS in production.** Create React App's dev proxy, Vite's proxy config, webpack-dev-server's proxy — these all work by forwarding requests through your local dev server, which sidesteps CORS entirely because the request appears to come from localhost. Everything works great locally. You deploy, and suddenly every API call breaks. It's not a deployment issue. You just never actually implemented CORS on the server because you didn't need to in development.


**Confusing browser CORS with server-to-server requests.** CORS is enforced by browsers. Not by servers. Not by curl. Not by Postman. If you're calling an API from your backend code (say, Node fetching from a third-party API), CORS doesn't apply at all. This trips up people who come from frontend-only backgrounds. If Postman works but your browser doesn't, that's CORS. If neither works, that's probably an authentication or firewall issue, not a CORS problem at all.


There's also a fourth mistake I keep seeing in StackOverflow answers: the "just disable CORS in Chrome" solution. You can launch Chrome with the `--disable-web-security` flag and it'll stop enforcing CORS. Don't do this for actual development. You're turning off a security feature in your browser so you can browse the web normally while your CORS issue silently follows you to production, where it will definitely still exist.


If you're curious how ToolsFuel's free developer tools handle cross-origin requests, or you want to experiment with building API calls yourself, you can check out everything in the
tools section. All tools run client-side with no external API calls, so CORS is never a factor — which is a nice bonus.

When You Don't Control the API Server

Sometimes you're calling a third-party API that doesn't send CORS headers. Maybe it's an internal microservice someone else owns. Maybe it's a legacy API that predates the modern web. You can't make the server add headers — what do you do?

The correct answer is a backend proxy. You write a simple server-side endpoint on your own domain that receives the request from your frontend, forwards it to the third-party API (server-to-server, so no CORS), and returns the response. Since your proxy is on the same domain as your frontend, there's no cross-origin issue.


This is exactly how most "serverless function" use cases work. An Netlify Function or Vercel Serverless Function acting as a proxy is a completely legitimate pattern, and it's what I'd recommend over any of the hacky browser extension solutions.


Speaking of which — CORS browser extensions. There are Chrome extensions that inject `Access-Control-Allow-Origin: *` into every response. These work for local development if you're in a pinch and the backend team is slow. Don't use them in production, obviously, and don't forget to disable them when you're done debugging, or you'll spend twenty minutes wondering why your carefully-written CORS policy seems to be doing nothing.


There's also a pattern with using a public CORS proxy service. I won't name them because I don't recommend it — you're routing your API requests through a stranger's server, which has all kinds of implications for data privacy and rate limiting. Set up your own proxy. It's genuinely not that much work.

Frequently Asked Questions

Why does CORS only happen in browsers and not in Postman or curl?

CORS is a browser security feature, not a server-side restriction. Browsers implement the Same-Origin Policy to protect users from malicious websites making requests on their behalf. Tools like Postman, curl, or server-side code don't make those same cross-origin checks because they're not acting as a user's browser session with potentially sensitive cookies and auth tokens. If Postman works but your browser doesn't, it's almost certainly a CORS problem. If both fail, you're likely looking at an authentication or server availability issue instead.

Can I just use Access-Control-Allow-Origin: * to fix CORS everywhere?

The wildcard works for public APIs that don't require authentication — it's perfectly valid for things like a public weather API or open data endpoint. But it won't work for credentialed requests (ones that send cookies or HTTP auth headers). When credentials are involved, you need to set the header to a specific origin, and you also need Access-Control-Allow-Credentials: true. Using the wildcard for authenticated endpoints gives you a false sense of security and won't actually work anyway, so there's no reason to do it.

What is a CORS preflight request and why is it showing up twice in my Network tab?

A preflight is an automatic OPTIONS request the browser sends before your actual request to verify the server allows the cross-origin call. It happens when your request uses methods beyond GET/HEAD/POST, uses headers beyond the standard allowed set, or sends JSON (application/json). The browser sends the preflight, waits for the server to confirm the request is allowed, then sends the real request. If you don't handle OPTIONS requests on your server and return the right headers, the preflight will fail and your actual request never goes through. Always handle OPTIONS explicitly, especially in frameworks that don't do it automatically.

How do I fix CORS in a React app calling a third-party API?

If you don't control the API server, you can't fix it from React — you need a backend proxy. Create a serverless function or a small API route (in Next.js, for example) on your own domain that forwards the request to the third-party API server-to-server. Since server-to-server requests aren't subject to CORS, this sidesteps the problem entirely. During development, you can use Vite's proxy config or Create React App's proxy setting in package.json to route requests through localhost, but remember that won't work in production — you'll need a real server-side proxy before deploying.

Where can I encode request headers or test URL strings for API debugging?

ToolsFuel's free [developer tools](/tools) include a URL encoder/decoder for checking percent-encoded values in request URLs and headers, a JWT decoder for inspecting authorization tokens, and an HTML entity encoder for escaping content in API payloads. All run entirely in your browser with no data sent to any server, which makes them safe for testing with real API keys or sensitive query strings. They're especially useful when you're debugging why a particular header value isn't parsing correctly on the server side.

Does CORS affect server-to-server API calls?

No. CORS is strictly a browser mechanism. If your Node.js backend calls an external API, your Python script fetches from a REST endpoint, or any server-side code makes HTTP requests, CORS doesn't apply at all. The restriction only kicks in when JavaScript running in a web browser tries to make a cross-origin request. This is why using a backend proxy fixes browser CORS issues — the browser request goes to your same-origin server, and your server makes the cross-origin call where CORS doesn't exist.

Try ToolsFuel

23+ free online tools for developers, designers, and everyone. No signup required.

Browse All Tools