How to Fix CORS Errors in Local Development

Last updated 2026-08-05

CORS is not a bug in your code and not a problem with the browser. It is the browser refusing to hand your JavaScript a cross-origin response that the server did not authorize. The request usually succeeded. What failed was reading it. That distinction explains why the fix is always a response header and never a change to your fetch call.

Read the error before you change anything

Message containsMeaningFix
No Access-Control-Allow-Origin header is presentthe server did not authorize your origin at allset that header to your exact origin
does not match the supplied originthe server allowed a different origin, often a hardcoded production URLset it to your dev origin
Response to preflight request does not passthe OPTIONS request failed, so the real request never happenedanswer the preflight with the allow headers below
credentials mode is includewildcard and credentials cannot be combinedecho the exact origin and add Access-Control-Allow-Credentials: true
Request header field ... is not allowedyou sent a header the server did not listadd it to Access-Control-Allow-Headers

The header rule that unblocks a local frontend

Match:  domain is api.example.com
Action: set response header  Access-Control-Allow-Origin: http://localhost:3000
        set response header  Access-Control-Allow-Credentials: true
        set response header  Access-Control-Allow-Headers: authorization, content-type
        set response header  Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS

Use your exact origin, including the scheme and port. Access-Control-Allow-Origin: * looks convenient and then fails the moment cookies are involved, because the specification forbids the wildcard on credentialed requests. Echoing one specific origin is both stricter and less likely to waste your afternoon.

Preflight

Anything beyond a simple GET or POST with ordinary headers triggers an OPTIONS preflight first. The browser sends it without your Authorization header, listing what it intends to send in Access-Control-Request-Headers. The response to that preflight has to authorize the method and the headers, which is why Access-Control-Allow-Headers and Access-Control-Allow-Methods belong in the rule even though the failing request is a GET. Adding Access-Control-Max-Age: 600 cuts the repeat preflights while you work.

If the server returns 404 or 405 for OPTIONS, headers alone will not save you. The browser requires a successful preflight response, and no extension can invent one where the server refuses to answer. That case needs a proxy or a server-side change.

Reading custom response headers

A cross-origin response only exposes a short list of headers to JavaScript. If your code needs to read X-Request-Id or a pagination header, the server has to list it in Access-Control-Expose-Headers, and until it does, response.headers.get returns null even though DevTools shows the header plainly. This one confuses everybody once.

Choose the right fix for the situation

ApproachGood forCost
Header rule in the browserunblocking yourself now, confirming that CORS is genuinely the problemlocal to you, has to be turned off later, fixes nothing for anyone else
Dev server proxy (Vite, Next.js, webpack)the everyday answer for a frontend calling an APIa few lines of config. Same-origin from the browser’s point of view, so CORS never applies
Server-side CORS configurationthe real fix, and the only one that works in productionneeds access to the API and a decision about which origins to trust
Disabling web security in a browser flagnothing. Do notturns off protections for every site in that profile
// vite.config.js: same-origin from the browser's point of view
export default {
  server: {
    proxy: { '/api': { target: 'https://api.example.com', changeOrigin: true } },
  },
};

A useful sequence: unblock with a header rule to confirm the diagnosis in thirty seconds, switch to a dev-server proxy for daily work, and open a ticket for the CORS configuration that actually ships. The header rule is a diagnostic, not a solution.

Things that look like CORS and are not

  • A failed request with no response at all: DNS, TLS, or the server being down. CORS errors involve a response you are not allowed to read.
  • A 401 or 403 from the API. Auth failures are not CORS failures, though a missing CORS header can hide the real status from your code.
  • Mixed content: an HTTPS page calling http:// is blocked by a different policy, and no CORS header helps.
  • A Content Security Policy connect-src rule blocking the call from your own page. The error text names CSP, not CORS.

Common questions

Does adding CORS headers in my browser fix CORS for my users?

No. The header is applied inside your browser after the response arrives, so only you see it. Every other visitor gets whatever the server sends. Use it to confirm the diagnosis, then fix the server or add a dev-server proxy.

Why does the request work in curl or Postman but fail in the browser?

Because CORS is enforced by browsers, not servers. curl and Postman have no origin to protect, so they never apply the check. A request that works in curl and fails in the browser is almost always a CORS problem rather than a broken endpoint.

Can I just use Access-Control-Allow-Origin: *?

For requests without credentials, yes. As soon as the request sends cookies or uses credentials mode include, the specification forbids the wildcard and the browser rejects the response. Echoing your exact origin works in both cases, so it is the better habit.

Why is my preflight failing when my GET request should be simple?

Something made it non-simple. A custom header such as Authorization, a content type other than the three simple ones, or a method beyond GET, HEAD, and POST all trigger a preflight. Look at the OPTIONS entry in the Network panel and see which header the server refused.