Fetch API
You’ve already used fetch for the everyday stuff: pick a method, set some headers, attach a body, read the response. That covers the vast majority of real requests. But fetch accepts a second argument with far more knobs than most people ever touch, and a few of them solve problems that are otherwise miserable to work around.
This chapter walks through the full options object. Treat it as a reference more than a tutorial.
Here’s every option fetch understands, shown with its default value. Alternatives sit in the comments:
let promise = fetch(url, {
method: "GET", // POST, PUT, DELETE, etc.
headers: {
// the Content-Type header is usually set for you,
// based on what kind of body you pass
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined, // string, FormData, Blob, BufferSource, or URLSearchParams
referrer: "about:client", // or "" to send no Referer header,
// or a url from the current origin
referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin...
mode: "cors", // same-origin, no-cors
credentials: "same-origin", // omit, include
cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached
redirect: "follow", // manual, error
integrity: "", // a hash, like "sha256-abcdef1234567890"
keepalive: false, // true
signal: undefined, // AbortController to abort request
window: window // null
});
Long list. The good news is that the defaults are sensible, so an empty options object (or none at all) behaves the way you’d expect for a normal request.
Three of these you already know well. method, headers, and body were covered back in Fetch. The signal option, used to cancel a request in flight, has its own chapter in Fetch: Abort. The rest are what we’ll unpack here.
referrer, referrerPolicy
These two control the HTTP Referer header — the header a browser attaches to tell the server which page the request came from. (The misspelling “Referer” is baked into the HTTP standard and can’t be fixed now.)
You usually don’t think about it. The browser fills it in automatically with the URL of the current page. Most of the time that’s harmless. Sometimes it leaks information you’d rather not send — an internal path, a query string with a token, the exact page a user was on — so it makes sense to trim or drop it.
The referrer option lets you set an exact Referer value (within your own origin) or remove it entirely.
To send no Referer at all, pass an empty string:
fetch('/page', {
referrer: "" // no Referer header
});
To point it at a specific URL — as long as that URL is on your current origin:
fetch('/page', {
// assuming the page lives on https://example.com,
// you can set any Referer you like, but only within this origin
referrer: "https://example.com/anotherpage"
});
The origin restriction is deliberate. If a page could forge a Referer claiming to come from any site, that header would be worthless as a source signal. So referrer only lets you rewrite it inside your own boundary.
The referrerPolicy option is the broader tool. Instead of naming one exact value, you hand the browser a rule and it decides what to send for each request based on where the request is going.
To reason about the rules, split requests into three cases:
That third case, dropping from HTTPS to plain HTTP, is treated with extra suspicion because a secure page shouldn’t spill its URL onto an unencrypted connection. The policy values come from the Referrer Policy specification:
"strict-origin-when-cross-origin"— the default. Same-origin gets the fullReferer; cross-origin gets only the origin; an HTTPS→HTTP request gets nothing."no-referrer-when-downgrade"— send the fullRefererevery time, except on an HTTPS→HTTP downgrade, where nothing goes."no-referrer"— never sendReferer, in any case."origin"— always send just the origin, never the path. Sohttp://site.cominstead ofhttp://site.com/path."origin-when-cross-origin"— fullRefererfor same-origin, origin-only for cross-origin."same-origin"— fullRefererfor same-origin, nothing for cross-origin."strict-origin"— send the origin only, and send nothing on an HTTPS→HTTP downgrade."unsafe-url"— always send the full URL, even on a downgrade. The name is a warning: this leaks the most.
The whole matrix in one table:
| Value | To same origin | To another origin | HTTPS→HTTP |
|---|---|---|---|
"no-referrer" |
- | - | - |
"no-referrer-when-downgrade" |
full | full | - |
"origin" |
origin | origin | origin |
"origin-when-cross-origin" |
full | origin | origin |
"same-origin" |
full | - | - |
"strict-origin" |
origin | origin | - |
"strict-origin-when-cross-origin" or "" (default) |
full | origin | - |
"unsafe-url" |
full | full | full |
A concrete case. Say your site has an admin area whose URL layout you’d rather not advertise. By default, a fetch to an outside server carries the full page URL in Referer:
Referer: https://example.com/admin/secret/paths
If you want outside sites to see only the origin, not the path, set the policy:
fetch('https://another.com/page', {
// ...
referrerPolicy: "origin-when-cross-origin" // Referer: https://example.com
});
You could apply this to every fetch in your app — for instance, by routing all requests through one wrapper function that sets it. Compared to the default, the only change is that cross-origin requests now send just the origin (https://example.com, no path). Same-origin requests still carry the full Referer, which can help when you’re reading your own server logs for debugging.
Rather than read the matrix top to bottom, try it. Pick a policy and a destination and watch exactly what Referer the browser would attach. The page you’re “on” is https://example.com/admin/reports?tab=3.
mode
The mode option is a guard rail against accidental cross-origin requests:
"cors"— the default. Cross-origin requests are allowed, following the CORS rules from Fetch: Cross-Origin Requests."same-origin"— cross-origin requests are forbidden outright."no-cors"— only “simple” cross-origin requests are allowed, and you can’t read the response body from JavaScript.
Where this earns its keep: the URL you’re fetching comes from somewhere you don’t fully trust — a third-party config, user input, a value from another service. Setting mode: "same-origin" gives you a hard “off switch” so that even if the URL points somewhere unexpected, the request can’t leave your origin.
credentials
The credentials option decides whether fetch sends cookies and HTTP Authorization headers along with the request.
"same-origin"— the default. Send them for same-origin requests, withhold them cross-origin."include"— always send them. For a cross-origin response to be readable in JavaScript, the server must reply withAccess-Control-Allow-Credentials, as covered in Fetch: Cross-Origin Requests."omit"— never send them, not even for same-origin requests.
cache
Out of the box, fetch plays by the same caching rules as any browser request. It honors Expires and Cache-Control, sends conditional headers like If-Modified-Since, and reuses cached responses when the headers say it may. Whether something is actually cached depends on those HTTP headers, not on fetch itself.
The cache option lets you step around that machinery or tune how it’s used:
"default"— standard HTTP cache behavior, respecting the usual headers."no-store"— ignore the HTTP cache completely: don’t read from it, don’t write to it. This also becomes the effective default if your request already carries one ofIf-Modified-Since,If-None-Match,If-Unmodified-Since,If-Match, orIf-Range."reload"— don’t read from the cache, but do store the fresh response (if its headers allow caching). Handy for a “hard refresh” of one resource."no-cache"— if there’s a cached copy, send a conditional request to check whether it’s still valid; otherwise send a normal request. Either way, store the response."force-cache"— use the cached response even if it’s stale. Only fall back to a network request when nothing is cached."only-if-cached"— use the cached response even if stale, and if there’s nothing cached, fail with an error instead of hitting the network. This one only works withmode: "same-origin".
redirect
When a server answers with a redirect (a 301, 302, and so on), fetch follows it for you by default. You get the final response, and the hop is invisible.
The redirect option changes that:
"follow"— the default. Follow redirects automatically."error"— treat a redirect as a failure and reject the promise."manual"— don’t follow, but don’t error either. You get a special response withresponse.type === "opaqueredirect", a zeroed status, and most properties empty. This lets you detect that a redirect happened and decide what to do, though the response body and target are deliberately hidden from you.
integrity
The integrity option asks fetch to verify that a response matches a checksum you already know. If the bytes don’t hash to what you specified, the request fails. It’s a defense against tampering or a corrupted download.
Per the Subresource Integrity specification, the supported hash functions are SHA-256, SHA-384, and SHA-512. A browser may support more.
Suppose you’re downloading a pinned library build and you know its SHA-256 checksum is "9c8d2f" (a real one is much longer). You attach it like this:
fetch('https://cdn.example.com/charting-lib.js', {
integrity: 'sha256-9c8d2f'
});
fetch computes the SHA-256 of what actually arrives and compares. Match, and you get the response as usual. Mismatch, and the promise rejects — you never see the suspect bytes.
Here’s the mechanism in miniature. Below, a file arrives and gets hashed; the result is compared against a checksum pinned up front. Change even one character of the “downloaded” bytes and the hash no longer matches — exactly the case where a real fetch would reject and hand you nothing. (The tiny hash here is a stand-in so you can read it; a real request uses SHA-256.)
keepalive
The keepalive option lets a request outlive the page that started it.
The classic use case is analytics on page exit. Say you’re collecting how a visitor used the page — clicks, which sections they scrolled to — and you want to send that summary to your server the moment they leave. You’d naturally hook into the unload:
window.onunload = function() {
fetch('/analytics', {
method: 'POST',
body: "session-summary",
keepalive: true
});
};
Normally this fails. When a document unloads, the browser tears down its pending network requests, and a fetch fired during unload would be cut off before it left. keepalive: true tells the browser to carry the request through in the background even after the page is gone. Without it, the request in that example wouldn’t reliably reach the server.
That power comes with two constraints:
- Bodies are capped at 64KB. You can’t ship megabytes on the way out.
- If you’re gathering a lot of data over the session, send it in batches as you go, so there’s little left to flush in the final
onunloadrequest. - The 64KB limit is shared across all in-flight
keepaliverequests at once. You can run several in parallel, but their body sizes have to add up to 64KB or less.
- If you’re gathering a lot of data over the session, send it in batches as you go, so there’s little left to flush in the final
- You can’t use the response once the page is gone. In the example, the
fetchsucceeds thanks tokeepalive, but any.thenyou attach to handle the reply won’t run — the JavaScript environment it belonged to no longer exists.- For fire-and-forget cases like analytics, that’s fine. The server takes the data and typically returns an empty response nobody needs to read.