Why Header Extensions Changed in Manifest V3
If a header extension you relied on stopped doing something, or started asking for permissions it never used to, the cause is almost certainly Manifest V3. Chrome replaced the API that extensions used to intercept network requests, and the replacement is deliberately less powerful. Understanding the boundary saves you from looking for a tool that cannot exist.
What changed
Under Manifest V2, an extension used the blocking form of webRequest: Chrome handed it every request, waited, and let it rewrite whatever it wanted. That is enormous power. It also means the extension sees all of your traffic and can slow every request down while it thinks.
Manifest V3 replaces it with declarativeNetRequest. The extension registers rules in advance, Chrome evaluates them itself, and the extension never sees the traffic. Chrome began turning off Manifest V2 extensions in 2024 and has since removed support in the stable channel, so this is now simply how header extensions work.
| Blocking webRequest (V2) | declarativeNetRequest (V3) | |
|---|---|---|
| Who applies the change | the extension, per request | Chrome, from pre-registered rules |
| Extension sees your traffic | yes | no |
| Modify request headers | yes | yes, with restrictions on append |
| Modify response headers | yes | yes |
| Read or rewrite response bodies | possible with extra work | no |
| Change the status code | yes | no |
| Rule count limits | none in practice | yes, including a cap on regex rules |
| Works while the extension is idle | no, it had to be running | yes, rules apply even when the worker is asleep |
The limits you will actually hit
- No response body access: this is the big one. Mocking a JSON response is not possible with declarativeNetRequest. Redirecting to a data URL or a local file is the nearest workaround, and full mocking needs a proxy or a tool with a native component.
- Host permissions are mandatory: Chrome will not modify a site’s requests without access to that site. A rule matched by domain can request one origin; a substring, wildcard, or regex rule could match anything, so it has to ask broadly. This is why enabling a rule prompts you.
appendis restricted on request headers: only an allowlist of request headers supports it, includingaccept,accept-language,cache-control,cookie,user-agent, andx-forwarded-for. For everything else, useset. Response headers are not restricted this way.- Rule limits and regex caps: there are ceilings on dynamic and session rules, and a separate cap on how many rules may use a regular expression. Ordinary use is nowhere near them, but a bulk import can be.
- Tab-scoped rules are session-only: restricting a rule to one tab requires a session rule, which disappears when the browser restarts. That is a Chrome constraint, not a product decision.
- No request log by default: seeing which rule matched which request needs an extra permission most extensions do not request. DevTools is the reliable source of truth.
What got better
- Privacy, meaningfully. An extension that modifies headers declaratively cannot read your traffic, so "does this tool see my requests" has an architectural answer rather than a promise in a policy.
- Reliability. Rules are applied by the browser, so they work on the very first request after a restart, before any extension code has run. Under V2, a sleeping or slow extension could miss requests.
- Performance. No per-request round trip into extension JavaScript.
- Reviewability. Declared rules are inspectable, which makes an extension’s behavior easier to audit than arbitrary interception code.
The trade is real and it is not all upside. Content blockers with very large or very dynamic rule sets lost capability, and that was the most contested part of the transition. For header editing specifically, the loss is narrower: response bodies and status codes, and little else.
Diagnosing "it used to work"
- Nothing applies at all: check whether host access was granted for the site. This is the most common cause after the migration.
appendsilently behaves likeseton a request header: the header is not on Chrome’s allowlist. Usesetdeliberately instead.- Response body mocking is gone: it is not coming back in an extension. Use a proxy or a dev-server mock.
- A tab-scoped rule disappeared after a restart: session rules do not persist. Re-enable it, or scope by domain instead.
- A large imported rule set partially applies: you may be against a rule or regex cap. Prune duplicates and prefer domain matches over regex.
- Two rules fight over one header: priority decides, and the loser leaves no trace. Give the rule you want a higher priority.
Common questions
Can Manifest V3 extensions still modify headers?
Yes. Setting, appending, and removing request and response headers is fully supported through declarativeNetRequest. What went away is arbitrary interception: reading and rewriting response bodies, changing status codes, and inspecting traffic as it passes.
Why does a header extension need access to all sites?
Because Chrome requires host access before applying a rule to a site, and a rule matched by substring, wildcard, or regex could match any host. Matching by domain lets a well-behaved extension ask for that one origin instead. Header Forge requests access only when you enable a rule, using the narrowest pattern the rule allows.
Is declarativeNetRequest less private or more private than webRequest?
More private. The extension never receives your requests or responses; it declares what should change and Chrome does the work. That is the core reason the API was designed this way, whatever you think of the trade-offs for content blocking.
Can I still mock API responses with an extension?
Not the body of a real response. You can redirect a request to a different URL, including a local file or a data URL, which covers some mocking use cases. For genuine response body mocking, use a proxy, a dev-server mock, or a tool with a desktop component.