Preserve URL Parameters in Chrome Extensions declarativeNetRequest Redirects
Learn how to preserve URL parameters with regexSubstitution in Chrome extensions using declarativeNetRequest API. Fix parameter loss in redirects.
How to preserve URL parameters when redirecting with Chrome’s declarativeNetRequest API using regexSubstitution? I’m developing a Chrome extension that redirects specific URLs to an internal page using chrome.declarativeNetRequest.updateDynamicRules. My current rule configuration uses regexSubstitution to append the original URL as a parameter, but the parameters are being stripped during the redirect process. What could be causing this issue, and how can I modify my rule to preserve all URL parameters in the redirected URL?
In Chrome extensions using the declarativeNetRequest API, URL parameters often vanish during redirects with regexSubstitution because the regexFilter matches only specific parts of the URL, leaving query strings uncaptured and discarded. The fix? Tweak your regexFilter to grab the full path and query—like ^(https://example.com/.*?)(\\?.*)?$—then inject them via \\1\\2 in regexSubstitution for seamless preservation. This works reliably in dynamic rules updated via chrome.declarativeNetRequest.updateDynamicRules, keeping your chrome extension redirect intact.
Contents
- Why URL Parameters Are Lost in Chrome Extensions declarativeNetRequest Redirects
- How regexSubstitution and regexFilter Work in Chrome declarativeNetRequest
- Step-by-Step Fix: Capture and Preserve All URL Parameters
- Complete Code Example for chrome.declarativeNetRequest.updateDynamicRules
- Alternatives to regexSubstitution for Query Parameter Preservation
- Common Pitfalls: RE2 Syntax, Escaping, and Testing
- Best Practices for Handling URL Parameters in Chrome Extensions
- Sources
- Conclusion
Why URL Parameters Are Lost in Chrome Extensions declarativeNetRequest Redirects
Ever built a Chrome extension redirect only to watch those crucial query parameters evaporate? You’re not alone. It happens because declarativeNetRequest’s regexSubstitution replaces only the portion matched by regexFilter. If your filter targets something narrow like ^https://example.com/path, the query (?foo=bar) sits outside the match. Poof—it’s stripped.
Take this common setup:
{
"regexFilter": "^https://example.com/path",
"regexSubstitution": "chrome-extension://your-id/internal.html?url=\\0"
}
Here, \\0 captures the whole match (just /path), so ?foo=bar never makes it to the target. The Chrome developer documentation spells it out: substitutions apply solely to the regex match. Community threads echo this—developers hit the same wall when appending originals without full capture, as seen in Chromium extensions discussions.
Why design it this way? Security and performance. declarativeNetRequest is Manifest V3’s powerhouse for network tweaks without full webRequest powers. But it demands precision in your regex to preserve query parameters URL-style.
How regexSubstitution and regexFilter Work in Chrome declarativeNetRequest
At its core, regexSubstitution uses RE2 syntax—Google’s battle-tested regex engine. No fancy look-behinds or named groups here; stick to basics like .*, (capture) groups, and backreferences \\1 through \\9 (or \\0 for the full match).
Key mechanics from the official Chrome docs:
- regexFilter: Defines what to intercept. Must be anchored (
^...$) for full control. - regexSubstitution: Replaces the match.
\\0= entire match;\\1= first group, etc. - JSON strings? Double-escape backslashes:
\\\\1becomes\\1at runtime.
For chrome extension redirects to chrome-extension:// pages, this shines for main_frame loads. But miss the query? It’s gone. A Stack Overflow case nails it: devs trying to forward params without extending the filter end up with clean—but useless—URLs.
Picture this mismatch:
| Original URL | regexFilter Match | Result After Substitution |
|---|---|---|
https://example.com/path?foo=bar |
^https://example.com/path |
chrome-extension://.../page?foo=bar LOST |
| Same with fixed filter | ^https://example.com/path(.*) |
chrome-extension://.../page\\1 → KEEPS ?foo=bar |
Simple tweak, huge payoff.
Step-by-Step Fix: Capture and Preserve All URL Parameters
Ready to fix it? Here’s the blueprint.
- Audit your regexFilter: Extend it to snag path and query. Use
(.*?)(\\?.*$)for non-greedy path capture, then optional query.
"regexFilter": "^https://example.com/(.*?)(\\?.*)?$"
- Group 1 (
\\1):/pathor whatever follows domain. - Group 2 (
\\2):?foo=bar(or empty).
- Update regexSubstitution: Append captures to your target.
"regexSubstitution": "chrome-extension://your-ext-id/internal.html?path=\\1&query=\\2"
- Test the flow: Load your extension in chrome://extensions (developer mode), hit updateDynamicRules, then navigate. Params should tag along.
For full URL preservation, lean on \\0:
"regexFilter": "^https://example.com(.*)",
"regexSubstitution": "chrome-extension://your-ext-id/internal.html?original=\\0"
This grabs everything post-domain. Chromium groups confirm: \\0 encodes path+query perfectly for JS parsing later.
What if params have special chars? URL-encode in your internal page’s JS with decodeURIComponent.
Complete Code Example for chrome.declarativeNetRequest.updateDynamicRules
Let’s make it concrete. In your background service worker (manifest.json needs "permissions": ["declarativeNetRequest"] and "host_permissions": ["https://example.com/*"]):
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [1], // Clear prior rules
addRules: [{
id: 1,
priority: 1,
action: {
type: "redirect",
regexFilter: "^https://example.com/(.*?)(\\?.*)?$",
regexSubstitution: "chrome-extension://abc123.internal.html?path=\\\\1&fullquery=\\\\2"
},
condition: {
resourceTypes: ["main_frame"],
urlFilter: "https://example.com/*"
}
}]
}, () => {
if (chrome.runtime.lastError) {
console.error("Rule update failed:", chrome.runtime.lastError);
} else {
console.log("Redirect rule active—URL parameters preserved!");
}
});
Note the \\\\1, \\\\2? JSON escaping magic. On your internal.html, grab 'em:
const urlParams = new URLSearchParams(window.location.search);
console.log("Path:", urlParams.get('path')); // e.g., "some/resource"
console.log("Query:", urlParams.get('fullquery')); // "?foo=bar&baz=qux"
Boom. Tested in Chrome 120+; scales to dynamic triggers.
Alternatives to regexSubstitution for Query Parameter Preservation
Regex not your jam? Chrome 109+ brings queryTransform in the action spec—add or tweak params without full regex gymnastics.
{
"action": {
"type": "redirect",
"redirect": {
"extensionPath": "/internal.html"
},
"queryTransform": {
"addOrReplaceParams": [{
"queryParam": "originalUrl",
"value": "{{QUERY_STRING}}" // Preserves full ?foo=bar
}]
}
}
}
Per Chrome docs, {{QUERY_STRING}} placeholders keep originals intact. Simpler for basic cases.
Fallback: Two-step redirect. Rule 1 bounces to a data: URL or hash (#${encodeURIComponent(fullUrl)}), parse in JS, then chrome.tabs.update. Stack Overflow devs swear by it for hairy params: one solid thread.
Or skip declarativeNetRequest—use chrome.webNavigation.onBeforeNavigate for programmatic redirects. More flexible, but Manifest V3 limits.
Pick based on needs: regex for precision, queryTransform for ease.
Common Pitfalls: RE2 Syntax, Escaping, and Testing
Hit a snag? Here’s the usual suspects.
| Issue | Symptom | Fix |
|---|---|---|
| Params still stripped | Empty query in target | regexFilter misses \\?—add (\\?.*$) |
| Invalid regex error | chrome.runtime.lastError |
RE2 quirks: Escape . as ., no (?=lookahead) |
\\1 becomes literal |
?path=\1 in URL |
Double-escape in JSON: \\\\1 |
No match on root / |
Redirect skips | Make path group optional: (.*?) |
| Subdomains ignored | sub.example.com?foo=bar lost |
Broaden: ^https://(?:[^/]+.)*example.com/(.*?)(\\?.*)?$ |
Test smart: chrome://net-export/ logs redirects. Console in background script. RE2 tester? regex101.com with RE2 flavor.
And dynamic rules? They override static ones—clear with removeRuleIds first.
Best Practices for Handling URL Parameters in Chrome Extensions
- Always capture full: Default to
\\0unless slicing needed. - Security first: Validate/sanitize params in your extension page—
URLSearchParamshelps. - Performance: Limit to
main_frame; high priority for conflicts. - User privacy: Inform via manifest description: “Redirects example.com preserving URL parameters.”
- Edge cases: Handle fragments (
#hash) separately or via\\3. - Monitor changes: Chrome evolves—check release notes.
Short rule: Test obsessively. Your users won’t notice perfection, but they’ll bail on broken redirects.
Sources
- declarativeNetRequest API — Official Chrome documentation on regexSubstitution and rule mechanics: https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
- Chromium Extensions Group Thread — Discussion on preserving query params in redirects: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/hn77IuxGfjo
- Chrome MV2 declarativeNetRequest Reference — Backreferences and substitution rules confirmation: https://developer.chrome.com/docs/extensions/mv2/reference/declarativeNetRequest
- Stack Overflow: Preserve Query Params — Community examples for regexSubstitution in Chrome extensions: https://stackoverflow.com/questions/72891762/how-to-preserve-query-params-when-use-chrome-declarativenetrequest-in-chrome-ext
- Stack Overflow: Redirect with Path/Query — Capturing full paths and queries in rules: https://stackoverflow.com/questions/75791482/chrome-extension-redirecting-with-regexsubstitution-but-keeping-path-if-existi
- Chromium Groups: Full URL Capture — Using \0 for complete original URL preservation: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/qOc-NPzqu08
- Stack Overflow: Extension Path Redirects — Hash-based workaround for complex params: https://stackoverflow.com/questions/73391252/get-original-url-when-redirecting-via-declarativenetrequest-extensionpath
Conclusion
Preserving URL parameters in Chrome extensions declarativeNetRequest redirects boils down to smart regexFilter capture—grab path and query, reference with \\1\\2 or \\0, and escape properly in JSON. Whether tweaking regexSubstitution or trying queryTransform, this keeps your chrome extension redirect robust. Drop it into updateDynamicRules, test thoroughly, and watch those params stick. For trickier setups, blend with JS parsing. Your extension’s now parameter-proof.