How to Modify HTTP Response Headers in Chrome

Last updated 2026-08-05

Changing a response header in the browser lets you answer "what would happen if the server sent this?" without deploying anything. It is how you test a Content Security Policy before it ships, reproduce a caching bug, or unblock a frontend while the API team is still writing the CORS change. The important thing to hold onto: you are changing what your browser sees, not what the server sends.

The three operations

OperationEffectUse it for
setreplaces the header, or adds it when absentthe default choice for almost everything
appendadds another value alongside the existing oneheaders that legitimately repeat, such as Set-Cookie
removestrips the header entirelytesting what breaks without it

On the request side, Chrome only permits append for a fixed list of headers (accept, accept-encoding, accept-language, cache-control, cookie, user-agent, x-forwarded-for and a few more). For any other request header, use set. Response headers do not have that restriction.

Matching the right requests

A rule is a match plus an action, and getting the match narrow is most of the work. Four kinds of match cover nearly everything:

  • Domain: matches a host and its subdomains. api.example.com covers v2.api.example.com too. This is the safest default, and it lets an extension request access to that origin only.
  • Contains: a substring anywhere in the URL, for example /graphql.
  • Wildcard: a pattern with *, plus the anchors || for a domain start, | for the URL start or end, and ^ for a separator: ||example.com^*/api/*.
  • Regex: full regular expressions when nothing simpler will do, and the only match type that supports capture-group substitution in redirects.

Narrow the match further with a resource type when you only care about one kind of traffic. Limiting a rule to xmlhttprequest leaves your page loads, images, and fonts untouched, which makes debugging much less confusing.

Worked examples

Allow an origin during local development

Match:  domain is api.example.com
Action: set response header  Access-Control-Allow-Origin: http://localhost:3000
        set response header  Access-Control-Allow-Credentials: true

Test a Content Security Policy before deploying it

Match:  domain is staging.example.com
Action: set response header  Content-Security-Policy:
          default-src 'self'; img-src 'self' data:; script-src 'self'

Rule out caching as the cause of a bug

Match:  contains /api/
Action: set response header  Cache-Control: no-store
        remove response header  ETag

What you cannot change this way

Manifest V3 extensions modify headers through Chrome’s declarativeNetRequest engine, which trades power for privacy: the extension declares changes instead of receiving your traffic. That boundary rules out several things people expect:

  • Response bodies: declarativeNetRequest cannot rewrite the body of a response, so no mocking a JSON payload. A redirect to a data URL or a local file is the usual workaround.
  • The status code: you cannot turn a 500 into a 200. The status line is not a header.
  • A log of matched requests: that needs a separate permission most extensions do not request, so DevTools remains your source of truth for what actually happened.
  • Requests outside the browser: other applications on your machine are unaffected. For those you need a proxy such as mitmproxy or Charles.

One more constraint that is easy to trip over: Chrome will not let an extension modify a site’s requests without host access to that site. Expect a permission prompt the first time you switch a rule on, and expect it to ask for broad access when your pattern could match any host.

Verify the change landed

  1. Open DevTools, go to Network, and reload with the panel open.
  2. Select the request and read the Response Headers section. Header modifications from extensions appear in the headers Chrome actually applied.
  3. Compare against curl -I <url>, which bypasses your extensions entirely. A difference between the two is your rule working.
  4. If nothing changed, check in this order: is the rule enabled, does the match really cover this URL, was host access granted, and is another rule with a higher priority touching the same header?

Because these changes are local, never treat one as a fix. A CORS header you added in your browser does nothing for your users, and a removed CSP is still enforced everywhere else. Use the local change to confirm the diagnosis, then make the change on the server.

Common questions

Can a Chrome extension modify response headers in Manifest V3?

Yes. Response header modification is supported by declarativeNetRequest, which is the Manifest V3 replacement for blocking webRequest. The extension declares which headers to set, append, or remove for which URLs, and Chrome applies them. What it cannot do is read or rewrite the response body.

Do these changes affect anyone else?

No. The modification happens inside your browser after the response arrives, so it affects only your session on your machine. The server sends what it always sent, and other users and other browsers see the original headers.

Why do I get a permission prompt when I enable a rule?

Chrome requires host access before an extension may modify a site’s requests. A rule matched by domain can ask for just that origin. A rule matched by substring, wildcard, or regex could match anything, so Chrome has to ask for broad access. Matching by domain where you can keeps the grant narrow.

Can I modify headers on requests my page makes to another origin?

Yes. Rules match on the request URL, not on the page you are viewing, so a rule for api.example.com applies to fetches your localhost page makes to that API. That is exactly why this technique works for CORS debugging.