How to Debug CORS Errors: A Practical Guide
CORS errors are one of the most misunderstood problems in web development. They look like client-side failures, but they are almost always fixed on the server. This guide walks through what the browser is actually telling you, how to find the real cause, and how to apply the correct fix without disabling security.
What CORS Actually Is
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls whether JavaScript running on one origin may read responses from another origin. An origin is the combination of scheme, host, and port — so https://app.example.com and https://api.example.com are different origins, and so are http://localhost:3000 and http://localhost:8080.
The key thing to internalize: CORS is enforced by the browser, not the server. The server still receives the request and often still processes it. The browser simply refuses to hand the response back to your JavaScript unless the server explicitly opts in with the right headers. This is why the same request works fine from curl or Postman but fails in the browser. For background, see our explainer on what CORS is and the related what is an API guide.
Read the Error Message Carefully
The exact wording in your browser console tells you which fix you need. Open DevTools and look at the Console tab. Common messages and what they mean:
- "No 'Access-Control-Allow-Origin' header is present" — the server returned a response, but it did not include the header that grants access. This is the most common case.
- "The value of the 'Access-Control-Allow-Origin' header ... must not be the wildcard '*' when the request's credentials mode is 'include'" — you are sending cookies or auth headers, which forbids using
*. - "Method ... is not allowed by Access-Control-Allow-Methods in preflight response" — your request method was not listed in the preflight response.
- "Request header field ... is not allowed by Access-Control-Allow-Headers" — a custom request header (like
AuthorizationorX-Api-Key) was not whitelisted.
Always check the Network tab too. If you see a request to your endpoint with the OPTIONS method appearing before your real request, you are dealing with a preflight — read on.
Simple Requests vs Preflighted Requests
Browsers split cross-origin requests into two categories, and knowing which one you have changes the fix.
Simple requests
A request is "simple" if it uses GET, HEAD, or POST, sends only a short list of safe headers, and (for POST) uses a content type of application/x-www-form-urlencoded, multipart/form-data, or text/plain. For these, the browser sends the request directly and only checks the response for Access-Control-Allow-Origin.
Preflighted requests
Anything else — a PUT or DELETE, a JSON body with Content-Type: application/json, or a custom header like Authorization — triggers a preflight. The browser first sends an OPTIONS request asking permission. Your server must answer that OPTIONS request with the right headers before the browser will send the real request. If your preflight returns a 404, a redirect, or a 401, the real request never fires. You can inspect what the server actually returns with our HTTP Header Inspector, and confirm the meaning of any status code with the HTTP Status Codes reference.
A Step-by-Step Debugging Process
Follow these in order. Most CORS problems resolve within the first three steps.
- Confirm it is really CORS. Reproduce the same request with
curl -i. If it succeeds outside the browser, it is a CORS configuration issue, not a broken endpoint. Our cURL to Code Converter helps you translate that working command back into fetch or Axios. - Identify the origin being sent. In the Network tab, open the failing request and read the
Originrequest header. The value the server must echo back is exactly this string — including scheme and port. - Inspect the response headers. Check whether
Access-Control-Allow-Originis present and whether it matches the origin (or is*). If it is missing, the server needs configuration. - Check for a preflight. If an
OPTIONSrequest is failing, make your server return204withAccess-Control-Allow-MethodsandAccess-Control-Allow-Headerscovering what your real request uses. - Handle credentials. If you send cookies or auth tokens, set
Access-Control-Allow-Credentials: trueand echo a specific origin — never*. - Generate the headers. Once you know what is missing, build a correct, copy-pasteable header set with our CORS Headers Builder or the CORS Headers Generator.
The Headers You Need to Set
A working configuration for an authenticated JSON API typically returns these response headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Access-Control-Max-Age tells the browser how long it may cache the preflight result, which reduces repeated OPTIONS requests. If your API serves multiple front-end origins, do not hardcode one value — read the incoming Origin header, validate it against an allowlist, and echo back the matched value. Reflecting any origin blindly defeats the purpose of CORS.
Common Mistakes That Keep CORS Broken
- Editing client code to fix a server problem. Adding headers to your
fetchrequest does nothing — only response headers from the server matter. - Using
*with credentials. The spec forbids it. Echo the specific origin instead. - Forgetting the preflight. Your
GEThandler may set CORS headers correctly, but if theOPTIONSroute is unhandled and returns 404, the browser blocks everything. - A redirect on the preflight. Browsers do not follow redirects on
OPTIONS; a 301/302 there fails silently. Make sure the exact URL (trailing slash and all) is what you call. - Confusing CORS with mixed content. An HTTPS page calling an HTTP endpoint is blocked for a different reason — see our mixed content fix guide.
- Disabling CORS in the browser as a "fix." Launching Chrome with security flags hides the problem only on your machine; every real user still fails.
Fixing CORS in Development
During local development, the cleanest approach is a same-origin proxy. Tools like the Vite, webpack, or Next.js dev server can proxy /api calls to your backend so the browser sees a single origin and CORS never applies. This mirrors a production setup where a reverse proxy or CDN sits in front of both your app and API on the same domain. The proxy approach is preferable to loosening server CORS rules just to unblock local work, because it keeps your production configuration honest and tightly scoped.
When you do need real CORS headers in production, configure them at the layer that owns the response — your application framework's CORS middleware, your reverse proxy, or your CDN edge — and validate the result with the header inspector before shipping.
Frequently Asked Questions
CORS is enforced only by browsers, not by HTTP clients like Postman or curl. Those tools send and receive the request normally because they do not implement the same-origin policy. The browser still lets the request reach your server, but it refuses to expose the response to your JavaScript unless the server returns the correct Access-Control-Allow-Origin header. A success in Postman confirms the endpoint works and points you toward a missing CORS response header as the real cause.
No. CORS is controlled entirely by response headers that the server sends back, so adding headers or options to your client request will not resolve it. The only client-side change that matters is whether you set credentials mode to include, which affects whether cookies are sent and which forbids a wildcard origin. The actual fix is to configure the server, reverse proxy, or CDN to return the right Access-Control-Allow-* headers.
A preflight is an automatic OPTIONS request the browser sends before your real request to ask the server for permission. It is triggered by anything beyond a simple request: methods like PUT or DELETE, a Content-Type of application/json, or custom headers such as Authorization. The server must answer the OPTIONS request with Access-Control-Allow-Methods and Access-Control-Allow-Headers that cover your real request. If the preflight returns an error, 404, or redirect, the real request is never sent.
The CORS specification prohibits combining the wildcard origin with credentials such as cookies or Authorization headers, because it would let any site make authenticated requests on a user's behalf. When the request uses credentials mode include, the server must echo back a specific origin string instead of *, and also send Access-Control-Allow-Credentials: true. The standard practice is to validate the incoming Origin header against an allowlist and reflect the matched value.
No. Launching a browser with web security disabled or using an extension that strips CORS only changes behavior on your own machine, so every real user still hits the error. It also exposes you to genuine cross-site attacks while that mode is active. For local development, use a same-origin dev proxy so the browser sees one origin; for production, configure proper CORS headers on the server scoped to the origins you trust.