Why Header Extensions Changed in Manifest V3

Last updated 2026-08-05

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 changethe extension, per requestChrome, from pre-registered rules
Extension sees your trafficyesno
Modify request headersyesyes, with restrictions on append
Modify response headersyesyes
Read or rewrite response bodiespossible with extra workno
Change the status codeyesno
Rule count limitsnone in practiceyes, including a cap on regex rules
Works while the extension is idleno, it had to be runningyes, 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.
  • append is restricted on request headers: only an allowlist of request headers supports it, including accept, accept-language, cache-control, cookie, user-agent, and x-forwarded-for. For everything else, use set. 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

  1. 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.
  2. 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.
  3. Performance. No per-request round trip into extension JavaScript.
  4. 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"

  1. Nothing applies at all: check whether host access was granted for the site. This is the most common cause after the migration.
  2. append silently behaves like set on a request header: the header is not on Chrome’s allowlist. Use set deliberately instead.
  3. Response body mocking is gone: it is not coming back in an extension. Use a proxy or a dev-server mock.
  4. A tab-scoped rule disappeared after a restart: session rules do not persist. Re-enable it, or scope by domain instead.
  5. A large imported rule set partially applies: you may be against a rule or regex cap. Prune duplicates and prefer domain matches over regex.
  6. 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.