Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

3.2 The CORS trap: no-cors by default, silent drops always

The safelist rule that decides a beacon's CORS mode, what opaque responses cost, why refused preflights are invisible, and the JSON-Blob story sorted from folklore.

Levels: Intermediate ~15 min read

Intermediate

Beacons cross origins constantly — that is their job. And the single decision that shapes every cross-origin beacon is one you never make directly: the request's CORS mode. The Beacon processing model starts every request in no-cors mode. The moment the derived Content-Type is not CORS-safelisted, it flips the request to cors mode. Everything about failure behavior follows from which side of that line your payload lands on.

The mode switch, from the spec's own steps

The processing model runs, in order: if data was given, extract it (deriving the Content-Type); if a Content-Type was derived, set corsMode to "cors"; then, if that Content-Type value is a CORS-safelisted request-header value, set corsMode back to "no-cors". The test for Content-Type, in plain language: the value must be at most 128 bytes, contain no CORS-unsafe bytes, parse as a MIME type, and its essence — the type/subtype pair, with parameters stripped — must be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain.

The runnable example implements the safelist test exactly as the spec defines it and walks each interesting value through the mode decision — including uppercase types (the MIME parse lowercases them) and the length gate:

Interactive Runnable example
Edit

Ready to run.

What no-cors actually means — and why failures are silent

In no-cors mode the request goes out as a simple form-style POST, and the response it gets back is an opaque filtered response — status 0, no headers, no body, unreadable by JavaScript. That is the trade the mode makes: you get to send cross-origin without any server CORS setup, and in exchange you can never learn anything about what came back. A beacon was never going to read its response anyway, so for telemetry this is usually the right trade.

The same opacity is what makes cors-mode failures invisible. When a payload flips the request into cors mode — a JSON-typed Blob, most commonly — the browser first performs a CORS preflight. If the server does not answer the preflight with the right headers, the request is dropped before any POST happens. Your code sees nothing: no exception, no rejection, no callback. sendBeacon returned true back when the data was queued, and that is the last you hear of it. Silent drops are not a bug in the API; they are the documented consequence of a fire-and-forget contract meeting an unconfigured server.

The JSON Blob story, told correctly

Here is where a decade of tutorial folklore needs careful sorting. You will read that sending a Blob typed application/json throws a TypeError in some browsers, with a specific version cited. What the specification says is unambiguous and different: the Blob is accepted, its Content-Type is not safelisted, the request switches to cors mode, a preflight is issued, and — if the server answers it — the POST proceeds. Individual browsers have at times thrown instead of proceeding, and those behaviors were real in those builds; but they are engine behaviors, not spec facts, they contradict each other across sources, and this series does not teach them as rules.

What you can rely on, always, is the decision procedure this page has already given you. Safelisted Content-Type → no-cors, no server setup needed, failures silent and rare. Non-safelisted → cors mode, preflight required, failures silent and certain if the server is not ready. If you want JSON semantics without the CORS machinery, the next page shows the two payload shapes that achieve exactly that.

Quick check

A Blob with type 'application/json' flips the beacon into which mode?

const blob = new Blob([json], { type: 'application/json' });
navigator.sendBeacon(url, blob);
// request mode = 

Key takeaways

  • Every beacon starts in no-cors; a non-safelisted derived Content-Type flips it to cors.
  • Safelisted = ≤128 bytes, safe bytes, and essence text/plain / x-www-form-urlencoded / multipart/form-data — parameters ignored.
  • no-cors responses are opaque: status 0, no headers, unreadable — the price of zero server setup.
  • A refused preflight drops the beacon silently; the boolean stays true either way. Verify important metrics server-side.
  • Blob-with-JSON throwing is engine folklore, not spec behavior; the spec's path is: accept, switch mode, preflight.
Was this page helpful?

Mark this learning resource complete to track your learning progress.