How to debug any CORS error: a systematic approach
Before we dive into specific CORS errors, here's something more useful — a systematic process for debugging any CORS error, including ones we don't cover.
This is the first post in a series where we’ll break down every common CORS error you’ll encounter in the browser. But before we get into specific errors, I want to give you something more useful: a framework for debugging any CORS error, including ones we don’t cover.
Because here’s the thing: you can memorize what every CORS error message means and still be slow at fixing them. The developers I’ve seen debug CORS quickly don’t work from a mental list of errors and fixes. They follow a process. They know where to look, what to check, and in what order. The specific error message is just the starting point.
That’s what this post is about.
Why CORS errors feel harder than they are
Most errors in web development give you something to work with. A stack trace. A line number. A response body with details. CORS errors give you almost nothing.
The browser intentionally hides the specifics from your JavaScript. You can’t programmatically inspect why a CORS request failed. All your catch block sees is a generic TypeError: Failed to fetch. The real information is only in the browser console, and even there, the messages can be cryptic.
On top of that, CORS errors can look identical on the surface but have completely different causes. “No Access-Control-Allow-Origin header” might mean your server isn’t sending the header, or it might mean a preflight failed before your actual request was even sent, or it might mean a redirect stripped the header along the way.
That’s why a systematic approach matters. Guessing is slow. Checking the right things in the right order is fast.
The four things to check, in order
When you hit a CORS error, open DevTools and work through these four steps. They’ll lead you to the root cause in the vast majority of cases.
1. Find the actual request in the Network tab
This sounds basic, but it’s where most people go wrong. They read the console error and start changing server configuration without ever looking at what the browser actually sent and received.
Open the Network tab and find the request that failed. Pay attention to a detail that catches a lot of people off guard: there might be two requests to the same URL.
If you see an OPTIONS request followed by your actual request (or followed by nothing), a preflight was involved. This changes the debugging path significantly — the CORS error might be about the preflight response, not your actual request. If you only see your actual request and no OPTIONS, the browser treated it as a simple request and the problem is in the response to that request directly.
This single check, “was there a preflight?”, immediately cuts your problem space in half.
2. Inspect the response headers
Click on the failed request (or the OPTIONS request if there was a preflight) and look at the response headers. You’re looking for these:
Access-Control-Allow-Origin — Is it present? Does it match your origin exactly? Not “close enough”, but exactly. https://myapp.com and https://www.myapp.com are different origins. https://myapp.com and https://myapp.com/ (with a trailing slash) are different values. If this header is missing entirely, the server isn’t configured for CORS at all.
Access-Control-Allow-Methods — Only relevant for preflight responses. Does it include the HTTP method your actual request uses? If your request is a PUT and this header only lists GET, POST, the preflight will fail.
Access-Control-Allow-Headers — Also only relevant for preflights. Does it include every non-standard header your request sends? A common miss: Content-Type needs to be listed here if you’re sending application/json, because that value isn’t in the set of “simple” content types the browser allows without a preflight.
Access-Control-Allow-Credentials — Only relevant if you’re sending cookies or auth headers. Must be true, and if it is, Access-Control-Allow-Origin can’t be *. This combination trips up a huge number of developers.
3. Check the request itself
Now look at what the browser sent. Specifically:
The Origin header. The browser adds this automatically. Whatever value is here is what the server’s Access-Control-Allow-Origin needs to match. Copy-paste it and compare, don’t eyeball it.
The method and headers. Are you sending custom headers like Authorization or X-Custom-Header? Is the Content-Type something other than text/plain, application/x-www-form-urlencoded, or multipart/form-data? Any of these will trigger a preflight, and the server needs to handle that.
Credentials. Did you set credentials: 'include' on the fetch call, or withCredentials: true on an XMLHttpRequest? If so, the rules tighten. Wildcard origins are no longer allowed and the server must explicitly opt into credentials.
4. Check for things that aren’t the server
Sometimes the server configuration is correct but something between the browser and the server is interfering:
Redirects. If the URL you’re fetching redirects to a different URL, CORS headers can get stripped along the way. Check if the Network tab shows a 301/302 before the final response.
Proxies and CDNs. Middleware, reverse proxies, or CDNs can strip or overwrite CORS headers. If your server code sets Access-Control-Allow-Origin but the response doesn’t include it, something in your infrastructure is eating it.
Browser extensions. Some extensions modify requests or responses in ways that break CORS. If something works in incognito but not in your normal browser, check your extensions.
Mixed content. If your page is served over HTTPS and you’re making a request to HTTP, the browser may block it before CORS is even relevant.
Reading the error message properly
With the above context, you’ll be able to make sense of what the browser is telling you. CORS error messages generally follow a pattern:
“No ‘Access-Control-Allow-Origin’ header is present” — The response doesn’t include the header at all. Either the server isn’t configured for CORS, a preflight failed, or something in the infrastructure is stripping the header.
“The ‘Access-Control-Allow-Origin’ header has a value that is not equal to the supplied origin” — The header is present but the value doesn’t match your origin. Could be a typo, a www vs non-www mismatch, a port mismatch, or the server is returning a hardcoded origin that doesn’t match yours.
“Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ’*’” — You’re sending credentials (cookies, auth headers) and the server is using a wildcard. These two are mutually exclusive by design — the server must echo the specific origin.
“Did not find method in CORS header ‘Access-Control-Allow-Methods’” — Your request uses a method (PUT, DELETE, PATCH) that the server’s preflight response doesn’t list as allowed.
“Missing token in CORS header ‘Access-Control-Allow-Headers’” — Your request includes a header that the server’s preflight response doesn’t list as allowed.
In the upcoming posts in this series, we’ll break down each of these errors in detail: what causes them, how to reproduce them, and how to fix them. If you want to go hands-on with any of them, the I-Hate-CORS course has dedicated labs where you can trigger each error in a live environment and fix it yourself.
A note on browser differences
Something worth keeping in mind as you go through this series: different browsers report CORS errors differently. Firefox tends to give the most descriptive messages (the “Reason:” format you might have seen). Chrome is more terse. Safari is often the least helpful.
The underlying behavior is the same, CORS is a spec, and modern browsers implement it consistently. But the error messages vary, so throughout this series, we’ll show you what the error looks like in each major browser so you can recognize it regardless of what you’re working in.
Coming up next
The first error we’ll tackle is the most common one: “CORS header ‘Access-Control-Allow-Origin’ missing.” It’s the error most developers see first, and it has more possible causes than you’d expect.
This is the approach we teach in the I-Hate-CORS course. Not just what each error means, but how to think through CORS problems systematically. If you want to try the hands-on debugging experience, the demo lab is free to try.