X-Requested-With header: who sends it, and why it's missing (tested)

X-Requested-With: XMLHttpRequest is a convention, not a browser feature. We checked which requests carry it.

1. What arrived at the server

RequestX-Requested-With
fetch('/api')(none)
plain new XMLHttpRequest()(none), despite the name
fetch('/api', {headers: {'X-Requested-With': 'XMLHttpRequest'}})XMLHttpRequest

The header only exists when code sets it. jQuery's $.ajax adds it automatically for same-origin requests, which is why older back-ends use it to tell "AJAX" from page loads (Laravel's $request->ajax(), Rails' request.xhr?). Switch that front-end to fetch or axios and the check silently fails: that is the "expected X-Requested-With header" / "header is missing" error. Fix it by sending the header yourself, or better, by checking Accept: application/json on the server.

2. Cross-origin: it triggers a preflight

Adding X-Requested-With to a cross-origin fetch made Chrome send an OPTIONS preflight first. When the server's response lacked Access-Control-Allow-Headers: X-Requested-With, the fetch failed and the real GET was never sent. With the header allowed, OPTIONS then GET both arrived. That's why some APIs and CDNs tell you to remove it, and why it is used as a cheap CSRF guard: other sites can't add it to a request without your server's permission.

// send it explicitly (same-origin, or cross-origin with Allow-Headers set)
fetch("/api/items", { headers: { "X-Requested-With": "XMLHttpRequest" } });

Cache preflights with Access-Control-Max-Age (measured).

3. Values seen in the wild

XMLHttpRequest is the only conventional value. Android WebView apps may send the app's package name instead (e.g. com.example.app), which is how sites spot in-app browsers. An empty value means some code set it to ""; treat it as absent.

Frequently asked questions

Does fetch send X-Requested-With?

No. In our Chrome test neither fetch() nor a plain XMLHttpRequest sent it; only code that sets the header does (jQuery does this for same-origin requests).

How do I fix 'X-Requested-With header is missing'?

Send it yourself: headers: {'X-Requested-With': 'XMLHttpRequest'}, or change the server check to look at Accept: application/json.

Why does X-Requested-With cause a CORS error?

It's not a CORS-safelisted header, so Chrome preflighted it; without Access-Control-Allow-Headers: X-Requested-With the request was blocked and never sent.

Is X-Requested-With a security feature?

Only as a CSRF hint: other origins can't add it without a successful preflight. Use real CSRF tokens or SameSite cookies as the actual defence.

Written with AI assistance. Measured on 2026-10-01 in Chromium 153 (full build): a local server logged the X-Requested-With header and method of same-origin fetch() and XMLHttpRequest calls, and of a cross-origin fetch() that set the header, with and without Access-Control-Allow-Headers. jQuery's, Laravel's, Rails' and Android WebView's behaviour is from their documentation, not this test. PlainHeaders is our extension; the other methods are free.