BugMojoBugMojoBugMojo
FeaturesPricingBlogGuidesAbout
Log inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Get started
  • Log in

Resources

  • Blog
  • Guides
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Privacy
  • Sitemap
  • Engineering
  • Playbooks
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Guides
  4. How to Debug CORS Errors: A Practical Field Guide
CORS Debugging

How to Debug CORS Errors: A Practical Field Guide

CORS is enforced only by the browser, and most fixes live on the server. Read the failing preflight and the console error together, and the right Access-Control-Allow-* header stops being a guess.

Hrishikesh BaidyaHrishikesh Baidya·Jun 5, 2026·8 min read
Guides
Isometric line-art of a browser split into a console panel with a red CORS error and a network panel showing a failing OPTIONS preflight linked to the blocked request, in lime on near-black
TL;DR

A CORS error is the browser refusing to expose a cross-origin response, not the network failing. The fix is almost always server-side: most of the ~15 named CORS error reasons MDN documents can only be resolved on the server. Read the two panels together — the Console names the reason (e.g. CORSMissingAllowOrigin), the Network tab shows the failing OPTIONS preflight and its response headers. The header the preflight omitted is your fix.

You wrote a fetch, it works in Postman, and the browser console lights up red: blocked by CORS policy. The reflex is to assume the request failed. It usually did not. Cross-Origin Resource Sharing is a browser-enforced rule, and a CORS error means the response came back but the browser will not hand it to your JavaScript because the server did not say your origin is allowed.

That single reframing changes the whole debugging loop. You stop poking at the client and start reading what the server returned. This guide covers what the error actually means, when the browser inserts a preflight OPTIONS request, how to read the failure across the Console and Network tabs, and the exact server-side header to add for each common misconfiguration. The reference throughout is MDN's CORS documentation, which enumerates the rules and the error strings precisely.

What does a CORS error actually mean?

A CORS error means the browser blocked a cross-origin response because the server did not return the headers authorizing your origin. The request often reached the server and ran; the browser simply withholds the response from your script. CORS is enforced only by browsers, so the same call succeeds in curl or Postman. Most fixes are server-side: send the correct Access-Control-Allow headers.

The same-origin policy is the default: a page at https://app.example.com cannot read a response from https://api.other.com unless that server opts in. CORS is the opt-in mechanism. The server grants access by returning an Access-Control-Allow-Origin header naming the requesting origin (or *). No header, no access. The browser blocks the read and prints a reason in the console.

This is why your API works in Postman and curl but not the browser: command-line clients do not implement the same-origin policy, so they never look for the header. That difference is the single most common source of confusion. The server is fine; the browser is doing its job. MDN states the consequence plainly: most CORS errors can only be resolved on the server, because the server controls whether cross-origin access is allowed.

Simple requests vs. preflighted requests

The browser splits cross-origin requests into two kinds, and knowing which one you are making tells you where to look. A request stays a simple request — no preflight — only when it uses GET, HEAD, or POST and sets nothing outside the CORS-safelisted request headers: Accept, Accept-Language, Content-Language, Content-Type, and Range. Anything else upgrades it to a preflighted request.

The trap most engineers hit is Content-Type. A POST stays simple only when its Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain. The moment you send a JSON body — application/json — the request is downgraded to a preflight. A custom Authorization header does the same thing. So your everyday JSON API call almost always triggers an OPTIONS preflight before the real request goes out.

FeatureRequest attributeStays simple (no preflight)Triggers preflight
HTTP methodVerb usedGET, HEAD, POSTPUT, PATCH, DELETE, etc.
Content-TypeBody media typeform-urlencoded, multipart, text/plainapplication/json and others
Custom headersHeaders set by codeOnly CORS-safelistedAuthorization, X-* custom headers
Two-sided trigger table. If any cell on the right is true, the browser sends an OPTIONS preflight before your request — and that OPTIONS is the row to inspect first.

A preflight is an automatic OPTIONS request the browser sends first, carrying Access-Control-Request-Method and Access-Control-Request-Headers to ask: will you accept this verb and these headers? The server must answer with matching Access-Control-Allow-Methods and Access-Control-Allow-Headers and a success status. If it does, the browser caches that answer for the duration of Access-Control-Max-Age — MDN documents a default of 5 seconds, with each browser enforcing its own internal maximum that overrides larger values — then sends the real request. If the preflight fails, the real request never leaves the browser.

Read it in the console and the network tab

Debug CORS with both DevTools panels open, because each tells you half the story. The Console gives you the reason string. The Network tab gives you the evidence. Chrome prints messages like No 'Access-Control-Allow-Origin' header is present on the requested resource; Firefox prints a named reason from MDN's list — CORSMissingAllowOrigin, CORSNotSupportingCredentials, CORSMethodNotFound, CORSMissingAllowHeaderFromPreflight, and about a dozen more. The reason string is not noise. It maps directly to the missing header.

Then switch to Network. For a preflighted request you will see two rows: a failed OPTIONS sitting just above the real request. Click the OPTIONS row and read its response headers. Is Access-Control-Allow-Origin present? Does it match your origin exactly? Does Access-Control-Allow-Headers include the header you sent? The absent or mismatched header is the bug. The request may show as (failed) or CORS error instead of a normal status code.

Key takeaway

Match the console reason to the preflight response. A reason of CORSMissingAllowHeaderFromPreflight means the OPTIONS response left out a header your request listed in Access-Control-Request-Headers. The console names the gap; the Network tab shows you the exact response that has the hole. You rarely need to guess.

Fixes by server, by error

Because the fix is server-side, the work is adding the right header to the response for the route the browser calls. For a missing Access-Control-Allow-Origin, MDN gives the canonical snippets. The wildcard * works only for non-credentialed requests; name the explicit origin when credentials are involved.

cors-fixes.confnginx
# --- Apache: missing Access-Control-Allow-Origin ---
# Header set Access-Control-Allow-Origin "https://app.example.com"

# --- Nginx: allow a named origin (use the explicit origin, not * with credentials) ---
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;

# --- Nginx: answer the preflight for a JSON PUT/PATCH/DELETE ---
location /api/ {
  if ($request_method = OPTIONS) {
    add_header 'Access-Control-Allow-Origin'  'https://app.example.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
    add_header 'Access-Control-Max-Age' 86400 always;
    return 204;
  }
  # ...proxy_pass to your app
}

# --- Credentialed request: explicit origin + the only valid credentials value ---
add_header 'Access-Control-Allow-Origin'      'https://app.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;  # 'true' is the ONLY valid value

Three rules cover most of the named errors. First, for CORSMethodNotFound on a verb like PUT, PATCH, or DELETE, add it to Access-Control-Allow-Methods — note that GET, HEAD, and POST are always permitted as CORS-safelisted methods even if you omit them, so a missing method only matters for the other verbs. Second, for CORSMissingAllowHeaderFromPreflight, the preflight response must include Access-Control-Allow-Headers covering whatever the request listed in Access-Control-Request-Headers (commonly Authorization or Content-Type). Third, for credentialed requests, never combine a wildcard with credentials.

Watch out

For a credentialed request (cookies, TLS client certs, or an Authorization header), the server must echo an explicit origin — never the * wildcard — and the only valid Access-Control-Allow-Credentials value is the case-sensitive string true. If you do not need credentials, omit that header entirely rather than setting false. A wildcard origin plus credentials is rejected by the browser because it would leak authenticated, user-specific data to any site.

If you do not control the server at all, you have two escapes MDN notes: route the call through a proxy you do control so the browser talks to a same-origin endpoint, or restructure the request as a simple request so it skips preflight. Neither is a substitute for fixing the upstream headers, but both unblock you.

Make the failure agent-readable

Every CORS guide ends here: open DevTools, read two panels, add a header. That loop assumes a human is sitting in front of the browser cross-referencing the console reason against the preflight response. The evidence is split across two places and exists only in that one DevTools session. Paste the console line into a chat with an AI agent and you have thrown away the half that matters — the preflight response headers that name the missing Allow-*.

This is the wedge BugMojo is built on. The browser extension captures the failing exchange as one artifact: the OPTIONS preflight with its response headers, the matching console CORS error string, and the originating fetch that triggered it, lined up together. The MCP server hands that bundle to an AI coding agent — Claude Code, Cursor — as structured evidence. The agent sees the preflight, sees that Access-Control-Allow-Origin is absent, sees the request that needed it, and names the exact server-side fix instead of guessing from a pasted error line.

FeatureBrowser DevToolsError monitoring (Sentry)BugMojo
Live, full DevTools network/console depth✓partialpartial
Mature production error aggregation & alerting—✓early
Preflight OPTIONS + console error captured as one artifactmanual—✓
Shareable one-click capture (no copy-paste)—partial✓
Failing exchange exposed to an AI agent over MCP——✓
Agent names the server-side fix from the evidence——✓
Honest two-sided view. DevTools and error-monitoring tools beat BugMojo on breadth and production maturity; the agent-readable capture that pairs the preflight with the console error is the row they do not have.
Common mistake

The classic mistake is debugging CORS from the console line alone. The reason string tells you what is missing; only the preflight response tells you the server actually omitted it on this route. Capture both together — for yourself and for any agent you hand it to — or you are reasoning from half the evidence.

A CORS error is the server's missing header echoed back by the browser. Read the preflight next to the console reason and the fix names itself.
BugMojo engineering
⁓ ⁓ ⁓
Capture the failing preflight and console error together

Install the free BugMojo extension to capture the OPTIONS preflight, the console CORS reason, and the originating fetch as one shareable artifact — then let an AI agent name the server-side fix over MCP. No project setup required.

Install the extension

Frequently asked questions

Frequently asked questions

Sources

  1. Cross-Origin Resource Sharing (CORS) — simple requests, CORS-safelisted methods and headers, preflight triggers, Access-Control-Max-Age default, and credentials rules — MDN Web Docs (2025)
  2. CORS errors — the named browser error strings (CORSMissingAllowOrigin, CORSNotSupportingCredentials, CORSMethodNotFound, etc.) and that most are resolved on the server — MDN Web Docs (2025)
  3. Reason: CORS header 'Access-Control-Allow-Origin' missing — cause and server fixes with Apache and Nginx config snippets — MDN Web Docs (2025)
  4. Access-Control-Allow-Credentials header — only valid value is true; omit rather than set false; cannot combine with a wildcard origin — MDN Web Docs (2025)
  5. Access-Control-Allow-Methods header — GET, HEAD, and POST are always allowed as CORS-safelisted methods regardless of the header — MDN Web Docs (2025)
  6. Access-Control-Allow-Headers header — required in the preflight response when the request carries Access-Control-Request-Headers — MDN Web Docs (2025)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • What does a CORS error actually mean?
  • Simple requests vs. preflighted requests
  • Read it in the console and the network tab
  • Fixes by server, by error
  • Make the failure agent-readable

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.