How to Change Sec-Fetch-Mode (and Other Sec-Fetch Headers)
On This Page
All Sec-Fetch-* headers are forbidden headers in the browser, meaning the browser controls them and silently ignores your attempts to override them. In this article, you’ll learn how to set them using a server-side proxy, and why the browser blocks them.
The Code
fetch("https://proxy.corsfix.com/?https://example.com/protected-page", { headers: { "x-corsfix-headers": JSON.stringify({ "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-Dest": "document", "Sec-Fetch-User": "?1", }), },});Instead of setting Sec-Fetch-* headers directly (which the browser ignores), you pass them inside x-corsfix-headers. Corsfix applies them server-side before forwarding the request to the target.
For local development, this works instantly without registration. For live websites, set up your domain (takes 30 seconds).
Indirect Option: Set the mode Option in fetch()
The value of Sec-Fetch-Mode is derived from the mode option you pass to fetch(). You can’t set the header directly, but you can influence it:
// Results in Sec-Fetch-Mode: corsfetch("https://api.example.com/data", { mode: "cors",});
// Results in Sec-Fetch-Mode: no-corsfetch("https://api.example.com/data", { mode: "no-cors",});
// Results in Sec-Fetch-Mode: same-originfetch("https://api.example.com/data", { mode: "same-origin",});This works for basic cases, but you can only choose from cors, no-cors, and same-origin. You cannot set it to navigate or websocket — those values are assigned by the browser based on how the request was initiated (clicking a link, typing in the address bar, establishing a WebSocket connection, etc.). There is no fetch() option that produces them.
This also only affects Sec-Fetch-Mode. You have no control over Sec-Fetch-Site, Sec-Fetch-Dest, or Sec-Fetch-User through any fetch() option.
Why You Can’t Set Sec-Fetch Headers Directly
You might try setting the header manually:
// This does NOT workfetch("https://api.example.com/data", { headers: { "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-Dest": "document", },});The browser won’t throw an error — it silently ignores those headers. Your request goes out with whatever Sec-Fetch-* values the browser decides, not what you specified.
Any header name starting with Sec- is classified as a forbidden request header by the Fetch specification. Forbidden headers can only be set by the browser (the “user agent”), never by JavaScript. The Sec- prefix stands for “secure” — a namespace reserved for headers that the browser guarantees came from itself, not from your code.
The security model of Sec-Fetch-* headers depends on servers being able to trust them. If JavaScript could set Sec-Fetch-Mode: navigate and Sec-Fetch-Site: none, any malicious script could impersonate a user typing a URL into the address bar.
What Each Sec-Fetch-Mode Value Means
The browser assigns one of these values to Sec-Fetch-Mode:
cors— The request uses the CORS protocol. This is whatfetch()sends by default for cross-origin requests.no-cors— The request skips CORS. Used when loading resources like images, scripts, or stylesheets via HTML tags. The response will be “opaque” (you can’t read its contents from JavaScript).navigate— The user is navigating to a page. Triggered by clicking links, typing in the address bar, submitting forms, or opening bookmarks.same-origin— The request targets the same origin and will fail if the target is a different origin.websocket— The request is initiating a WebSocket connection.
navigate is the value most servers use to identify “real” browser traffic, and it’s the one you cannot produce from fetch().
All Sec-Fetch Headers Are Forbidden
Sec-Fetch-Mode isn’t the only header you can’t control. The browser sends four Sec-Fetch-* headers, and they’re all forbidden:
Sec-Fetch-Mode tells the server the request’s mode (cors, navigate, no-cors, same-origin, websocket).
Sec-Fetch-Site tells the server the relationship between your origin and the target (same-origin, same-site, cross-site, none).
Sec-Fetch-Dest tells the server what type of resource is being requested (document, image, script, empty, and many more).
Sec-Fetch-User tells the server whether the request was triggered by a user action. Its value is always ?1 when present, and it’s omitted entirely for non-user-initiated requests.
Servers that enforce fetch metadata policies check combinations of these headers. A request that looks like a real user navigating to a page has:
Sec-Fetch-Mode: navigateSec-Fetch-Site: noneSec-Fetch-Dest: documentSec-Fetch-User: ?1A cross-origin fetch() call from JavaScript looks like this:
Sec-Fetch-Mode: corsSec-Fetch-Site: cross-siteSec-Fetch-Dest: emptyThese two fingerprints are completely different, and there’s nothing you can do from the browser to make the second one look like the first.
Servers That Check Sec-Fetch Headers
Some servers use these headers to reject requests that don’t look like normal browser navigation. Google’s own guidance recommends using Sec-Fetch-* headers to build a “Resource Isolation Policy” that blocks cross-origin requests that aren’t top-level navigations. This defends against CSRF, cross-site script inclusion, and data leaks.
If you’re calling one of these endpoints from frontend JavaScript — a client-side integration, a third-party service, or a scraping target — your requests get rejected. The server sees Sec-Fetch-Site: cross-site and Sec-Fetch-Mode: cors, and blocks you. From the browser, you can’t change these headers. A server-side proxy like Corsfix can.
Other Ways to Override Sec-Fetch Headers (Dev and Testing)
If you’re not building for production and just need to test or debug, there are simpler options.
Browser extensions like ModHeader or Requestly can intercept outgoing requests and override any header, including forbidden ones. They work at the browser level, below the Fetch API’s restrictions. This is great for testing how a server responds to different Sec-Fetch-* values without writing any code.
curl and Postman don’t have any concept of forbidden headers. You can set whatever you want:
curl "https://example.com/protected-page" \ -H "Sec-Fetch-Mode: navigate" \ -H "Sec-Fetch-Site: none" \ -H "Sec-Fetch-Dest: document" \ -H "Sec-Fetch-User: ?1"Server-side code (Node.js, Python, Go, etc.) also has no forbidden header restrictions. In fact, server-side HTTP clients don’t even add Sec-Fetch-* headers by default — they’re a browser-only feature. You can set them manually if the target server requires them, or simply omit them (many servers allow requests that don’t include fetch metadata at all, since they need to support older browsers and non-browser clients).
These approaches all work for development and testing, but they don’t help when you need to make requests from client-side JavaScript running in a real user’s browser.
Conclusion
Use a server-side proxy like Corsfix to change Sec-Fetch-Mode and other Sec-Fetch-* headers from JavaScript. Pass the headers you need inside x-corsfix-headers, and Corsfix sets them on the outbound request before forwarding it to the target.
Corsfix handles Sec-Fetch-* overrides along with other forbidden headers server-side, so you can focus on building your app instead of managing proxy infrastructure.