The journal
Preflight vs XHR: what’s the difference?
A clear, practical explanation of how a CORS preflight (OPTIONS) differs from an XMLHttpRequest (XHR), when browsers send preflights, and how that affects your requests.

A CORS preflight is an automatic HTTP OPTIONS request the browser sends to check cross-origin permissions; XMLHttpRequest (XHR) is the JavaScript API that performs the actual HTTP request. If the preflight is denied, the browser does not send the actual cross-origin HTTP request. See MDN: Preflight request and MDN: XMLHttpRequest.
How a CORS preflight works
When a cross-origin request is not considered “simple,” the browser automatically issues a preflight. This is a CORS request using the OPTIONS method that asks the server whether the intended method and headers are allowed. The browser includes:
OriginAccess-Control-Request-Method(the method it intends to use)Access-Control-Request-Headers(optional list of request header names)
If the server permits the request, it responds with CORS headers such as Access-Control-Allow-Origin, Access-Control-Allow-Methods, and (when relevant) Access-Control-Allow-Headers. The browser may cache the preflight decision using Access-Control-Max-Age in a dedicated preflight cache (not the general HTTP cache). Source: MDN: Preflight request.

What XHR is (and is not)
XMLHttpRequest is the client API used to send the actual HTTP request and receive a response. It can make same-origin or cross-origin calls, but permission for certain cross-origin calls is enforced by the browser via the preflight step. XHR itself does not negotiate CORS rules; it is just the transport used after the browser allows the request. Source: MDN: XMLHttpRequest.
Typical sequence you’ll see in DevTools
- Browser sends OPTIONS (preflight) with the intended method and headers.
- Server replies with
Access-Control-Allow-*headers that must permit the origin, method, and any non-simple headers. - If allowed, the browser proceeds to send the actual XHR (or other client API) request; if not, the browser blocks it and reports a CORS error. Source: MDN: Preflight request.
What triggers a preflight (and how to avoid unnecessary ones)
Preflights occur for non-simple cross-origin requests. Common triggers include:
- Using methods beyond simple ones (for example, a DELETE like in MDN’s example). MDN: Preflight request
- Sending custom request headers (for example,
X-Requested-Within the MDN example). MDN: Preflight request
To avoid avoidable preflights, keep cross-origin requests within “simple” constraints where that’s acceptable and remove unnecessary custom headers. Otherwise, configure your server to reply correctly to the preflight.
Diagnose and fix a blocked cross-origin XHR
Example: adding a custom header
If your client adds a header such as X-Requested-With, the browser will preflight. The preflight includes Access-Control-Request-Headers: X-Requested-With, and the server must reply allowing that header via Access-Control-Allow-Headers. Only after a permitting response does the browser send the actual XHR. Source: MDN: Preflight request.

Related reading on Stack & Method
Short FAQ
Does preflight happen for same-origin calls?
No. A preflight is a CORS request and only applies to cross-origin conditions. Source: MDN: Preflight request.
Is preflight tied to XHR only?
No. Preflight is a browser behavior for qualifying cross-origin requests. XHR is just one way to send the actual request afterward. Source: MDN: Preflight request, MDN: XMLHttpRequest.
In practice: inspect the OPTIONS preflight first, correct the server’s Access-Control-Allow-* headers, then retry the actual XHR once the browser grants permission.