Technical Implementation of HTTP Request Interception in a Browser Extension
A client comes with a requirement: block telemetry scripts, add authorization headers to internal APIs, and redirect all media requests to a fast CDN. Without HTTP request interception, it's impossible. We've implemented dozens of such extensions and know all the pitfalls—from Manifest V3 limitations to cross-browser compatibility nuances.
Previously, developers actively used chrome.webRequest with the blocking flag, but with the transition to Manifest V3, this method lost flexibility. The modern standard is declarativeNetRequest, a declarative API that shifts rule processing to the browser level. It's faster, more secure, and doesn't load the main thread, but imposes strict limits: no more than 5,000 rules can be active simultaneously. Let's dive into how to design a rule system to overcome these limits while keeping full control over traffic.
How to Intercept Requests in MV3?
The foundation is a declarative approach: you describe rules in JSON, and the browser applies them. The extension doesn't spend CPU analyzing each request, so Core Web Vitals are not harmed.
Static Rules
Rules defined in the rules/static.json file apply constantly. Example configuration for blocking analytics, modifying headers, and redirecting:
[ { "id": 1, "priority": 1, "action": { "type": "block" }, "condition": { "urlFilter": "||analytics.example.com^", "resourceTypes": ["script", "xmlhttprequest", "image"] } }, { "id": 2, "priority": 2, "action": { "type": "modifyHeaders", "requestHeaders": [ { "header": "X-Custom-Token", "operation": "set", "value": "my-token" }, { "header": "Referer", "operation": "remove" } ] }, "condition": { "urlFilter": "https://api.internal.corp/*", "resourceTypes": ["xmlhttprequest"] } }, { "id": 3, "priority": 1, "action": { "type": "redirect", "redirect": { "regexSubstitution": "https://cdn.example.com\\1" } }, "condition": { "regexFilter": "^https://slow-cdn\\.com(.*)", "resourceTypes": ["image", "media", "font"] } } ] Limits: up to 30,000 static rules total, with no more than 5,000 active at once. The rest can be dynamically activated via the service worker.
Dynamic Rules
If you need to add rules on the fly—use the service worker. For example, blocking/unblocking a domain or injecting an authorization token:
// background/sw.js async function blockDomain(domain) { const existingRules = await chrome.declarativeNetRequest.getDynamicRules(); const maxId = existingRules.reduce((max, r) => Math.max(max, r.id), 0); await chrome.declarativeNetRequest.updateDynamicRules({ addRules: [{ id: maxId + 1, priority: 10, action: { type: 'block' }, condition: { urlFilter: `||${domain}^`, resourceTypes: [ 'main_frame', 'sub_frame', 'script', 'stylesheet', 'image', 'xmlhttprequest', 'other' ] } }], removeRuleIds: [] }); } How to Modify the Request Body via Content Script?
declarativeNetRequest does not allow modifying the body. For that, use a content script with world 'MAIN', intercepting fetch and XMLHttpRequest. Example injecting a field into a POST request body:
const originalFetch = window.fetch; window.fetch = async function(input, init = {}) { const url = typeof input === 'string' ? input : input.url; if (url.includes('api.target.com')) { init.headers = { ...init.headers, 'X-Injected-Header': 'value', }; if (init.body) { const body = JSON.parse(init.body); body.extraField = 'injected'; init.body = JSON.stringify(body); } } return originalFetch.call(this, input, init); }; Note: monkey-patching does not work for requests from web workers and WebSocket. For full traffic control (enterprise proxies), Chrome's enterprise policy still allows MV2, but the public Chrome Web Store does not accept it.
Why is declarativeNetRequest Faster Than webRequest?
The main difference is native rule processing by the browser without JavaScript involvement. WebRequest requires synchronous handling of each request in the extension, which delays the response and worsens TTFB. declarativeNetRequest processes rules at the network stack level, reducing LCP by 10–20% in typical cases.
| Characteristic | webRequest (MV2) | declarativeNetRequest (MV3) |
|---|---|---|
| Body modification | Yes (via blocking) | No |
| Performance | Lower (JS processing) | Higher (native browser) |
| Security | Lower (potential XSS) | Higher (isolated rules) |
| Dynamic rules | Via listener | updateDynamicRules |
| Redirects with substitution | Yes | Yes (regexSubstitution) |
Additional Table: When to Use Static vs Dynamic Rules
| Rule Type | When to Use | Example |
|---|---|---|
| Static | Fixed scenarios not requiring frequent changes | Block known trackers, modify headers for enterprise services |
| Dynamic | Configuration changes: enable/disable on user request, temporary tokens | Switch between staging and production, add authorization with limited lifespan |
What Are the Limitations of declarativeNetRequest?
Besides the rule count limit (5,000 enabled), remember: dynamic rules can only be added from the service worker, not from the popup or content script. Also, all rules must be declared in advance—you cannot generate a rule based on arbitrary JS computation. For debugging, use chrome.declarativeNetRequest.getMatchedRules() and testMatchOutcome(). These methods help verify which rule will fire for a specific URL and identify conflicts.
What Our Implementation Includes
- Architecture: designing a rule system (static + dynamic) respecting MV3 limits.
- Content scripts: monkey-patching for request body modification when needed.
- Debugging: testing rules via
testMatchOutcome, logging firings. - Documentation: full description of all rules, redirection scheme.
- Support: post-launch maintenance, updates when APIs change.
Our Work Process
- Analytics — study your application's request structure, identify interception targets.
- Design — create a rule specification, align with you.
- Implementation — write extension code, including service worker and content scripts.
- Testing — validate on real scenarios, catch edge cases.
- Deployment — publish to Chrome Web Store (or enterprise registry).
Timeline and Guarantees
Development timeline: from 5 to 15 working days depending on complexity. We guarantee stable operation and compliance with Chrome Web Store policies. Experience: over 50 browser extension projects.
For detailed information, refer to the official declarativeNetRequest documentation. Get a consultation for your project—contact us to discuss.







