. Google Search uses the udm query parameter to control result formatting. Setting udm=14 forces the classic web results layout, suppressing AI Overviews and conversational surfaces. Instead of hiding these elements after they render, we rewrite the request before it leaves the browser.
Manifest V3 requires explicit declaration of the declarativeNetRequest permission and host permissions for the target domain. The extension must also declare a rule resources file.
// manifest.json
{
"manifest_version": 3,
"name": "SearchRequestInterceptor",
"version": "1.0.0",
"permissions": [
"declarativeNetRequest",
"storage"
],
"host_permissions": [
"*://www.google.com/*",
"*://www.google.*/*"
],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_suppress_ai",
"enabled": true,
"path": "rules/navigation_rules.json"
}
]
},
"background": {
"service_worker": "src/background.ts"
},
"content_scripts": [
{
"matches": ["*://www.google.com/search?*"],
"js": ["src/fallback_guard.ts"],
"run_at": "document_idle"
}
]
}
Step 2: Define the Network Rewrite Rule
The core mechanism uses declarativeNetRequest to transform the query string. We target main_frame requests to avoid interfering with XHR/fetch calls made by the page's internal JavaScript. The rule uses addOrReplaceParams to inject udm=14.
// rules/navigation_rules.json
[
{
"id": 1001,
"priority": 10,
"action": {
"type": "redirect",
"redirect": {
"transform": {
"queryTransform": {
"addOrReplaceParams": [
{ "key": "udm", "value": "14" }
]
}
}
}
},
"condition": {
"urlFilter": "|https://www.google.com/search?",
"requestDomains": ["www.google.com"],
"resourceTypes": ["main_frame"],
"requestMethods": ["GET"]
}
}
]
Step 3: Implement Exception Handling for On-Demand Access
Users may occasionally want to view AI results for a single query. We implement a higher-priority rule that bypasses the rewrite when a specific flag is present. This keeps exception logic at the same layer as the primary rule.
// rules/navigation_rules.json (continued)
[
{
"id": 1002,
"priority": 20,
"action": {
"type": "allow"
},
"condition": {
"urlFilter": "|https://www.google.com/search?*&ai_override=1",
"requestDomains": ["www.google.com"],
"resourceTypes": ["main_frame"],
"requestMethods": ["GET"]
}
}
]
Step 4: Demote Content Script to Fallback Guard
The content script is no longer responsible for primary suppression. It runs as a safety net to detect unexpected markup changes or parameter behavior shifts. If the network rule fails to suppress content, the guard hides it and logs a telemetry event.
// src/fallback_guard.ts
const AI_PANEL_SELECTORS = [
'[data-ai-overview]',
'.ai-overview-panel',
'[role="region"][aria-label*="AI"]'
];
function initializeFallbackGuard(): void {
const observer = new MutationObserver((mutations) => {
const hasRelevantChange = mutations.some(m =>
m.addedNodes.length > 0 || m.attributeName === 'class'
);
if (hasRelevantChange) {
const target = AI_PANEL_SELECTORS
.map(sel => document.querySelector(sel))
.find(el => el !== null);
if (target) {
target.setAttribute('data-suppressed', 'true');
target.style.setProperty('display', 'none', 'important');
console.warn('[SearchInterceptor] Fallback guard activated. Network rule may need review.');
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'data-*']
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeFallbackGuard);
} else {
initializeFallbackGuard();
}
Architecture Rationale
- Why
declarativeNetRequest over webRequest? MV3 deprecated blocking webRequest for performance and security reasons. declarativeNetRequest operates in the browser's networking stack without exposing traffic to the extension process, reducing memory overhead and eliminating race conditions.
- Why
main_frame only? Restricting to top-level navigations prevents the rule from intercepting internal API calls, image requests, or analytics pings. This maintains page functionality while controlling the initial payload.
- Why keep a content script? Upstream parameters can change behavior without warning. The fallback guard provides observability and ensures graceful degradation rather than silent failure.
Pitfall Guide
1. Over-Matching resourceTypes
Explanation: Developers often omit resourceTypes or set it to ["xmlhttprequest", "sub_frame"], causing the rule to rewrite internal fetch calls or iframe loads. This breaks page interactivity, autocomplete, and lazy-loaded components.
Fix: Always restrict to ["main_frame"] for navigation-level parameter injection. Validate with Chrome DevTools Network tab to ensure only top-level documents are redirected.
2. Ignoring Rule Priority Conflicts
Explanation: declarativeNetRequest evaluates rules by priority (higher number = higher priority). If an exception rule shares the same priority as the suppression rule, behavior becomes non-deterministic.
Fix: Assign explicit priority tiers. Use 10 for base suppression, 20 for user overrides, and 5 for logging/telemetry. Document priority mapping in the extension's architecture notes.
3. Assuming All Navigations Trigger Extension Hooks
Explanation: Certain embedded surfaces, client-side route changes, or cross-origin redirects bypass chrome.tabs.onUpdated and MutationObserver. Developers waste time debugging why their observer isn't firing.
Fix: Accept that some surfaces are architecturally invisible to extensions. Design prevention mechanisms (like hiding trigger buttons) rather than relying on post-navigation detection.
4. Hardcoding CSS Selectors as Primary Logic
Explanation: Using class names or data attributes as the main suppression mechanism ties the extension to Google's frontend implementation. Obfuscation pipelines change these daily.
Fix: Treat DOM selectors as telemetry signals only. Primary control must always reside at the request or configuration layer.
5. Exceeding MV3 Static Rule Limits
Explanation: Chrome enforces a limit of 5,000 static rules per extension. Developers building complex modifier suites often hit this ceiling, causing silent rule drops.
Fix: Consolidate rules using wildcards and regex filters. Use dynamicRules API for user-configurable overrides, keeping static rules focused on core functionality. Monitor rule count during CI builds.
6. Not Handling Direct URL Entry
Explanation: Users typing https://www.google.com/search?q=test&udm=50 directly into the address bar bypass the search box flow. The network rule may not trigger if the URL already contains the parameter.
Fix: Use addOrReplaceParams instead of addParams. This overwrites existing values. Additionally, implement a chrome.tabs.onUpdated listener to detect direct navigation and apply a programmatic redirect if necessary.
7. Blocking Legitimate Sub-Resources
Explanation: Overly broad urlFilter patterns like *google.com* can intercept font requests, favicon loads, or CDN assets, causing visual corruption or failed page loads.
Fix: Anchor filters to specific paths (/search?) and validate against known resource patterns. Use requestMethods: ["GET"] to avoid interfering with POST-based form submissions.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Suppressing AI Overviews on search | Network rewrite (udm=14) | Prevents payload generation; zero render flicker | Low (single rule, minimal maintenance) |
| Handling user on-demand AI access | Higher-priority allow rule | Keeps exception logic at same layer; avoids DOM race | Low (priority-based routing) |
| Detecting upstream markup changes | Fallback content script + telemetry | Provides observability without primary responsibility | Medium (requires selector maintenance) |
| Blocking native browser UI elements | Not possible via extension APIs | Native UI operates outside extension sandbox | N/A (architectural limit) |
| Managing complex modifier suites | Consolidated static rules + dynamic overrides | Stays within MV3 5,000 rule limit; scales cleanly | Medium (requires rule management system) |
Configuration Template
// manifest.json
{
"manifest_version": 3,
"name": "RequestInterceptor",
"version": "1.0.0",
"permissions": ["declarativeNetRequest", "storage"],
"host_permissions": ["*://www.google.com/*", "*://www.google.*/*"],
"declarative_net_request": {
"rule_resources": [{
"id": "core_rules",
"enabled": true,
"path": "rules/core.json"
}]
},
"background": { "service_worker": "dist/background.js" },
"content_scripts": [{
"matches": ["*://www.google.com/search?*"],
"js": ["dist/fallback.js"],
"run_at": "document_idle"
}]
}
// rules/core.json
[
{
"id": 1001,
"priority": 10,
"action": {
"type": "redirect",
"redirect": {
"transform": {
"queryTransform": {
"addOrReplaceParams": [{ "key": "udm", "value": "14" }]
}
}
}
},
"condition": {
"urlFilter": "|https://www.google.com/search?",
"requestDomains": ["www.google.com"],
"resourceTypes": ["main_frame"],
"requestMethods": ["GET"]
}
},
{
"id": 1002,
"priority": 20,
"action": { "type": "allow" },
"condition": {
"urlFilter": "|https://www.google.com/search?*&override=1",
"requestDomains": ["www.google.com"],
"resourceTypes": ["main_frame"],
"requestMethods": ["GET"]
}
}
]
Quick Start Guide
- Initialize Project Structure: Create
manifest.json, rules/core.json, src/background.ts, and src/fallback_guard.ts. Configure TypeScript compilation to output to dist/.
- Load Extension in Chrome: Navigate to
chrome://extensions, enable Developer Mode, click "Load unpacked", and select the project root. Verify the extension appears in the toolbar.
- Validate Rule Registration: Open DevTools β Application β Service Workers β inspect the background page. Run
chrome.declarativeNetRequest.getRules() to confirm both rules are active and prioritized correctly.
- Test Navigation Flow: Perform a search via the address bar and the search box. Verify the URL contains
udm=14, the network tab shows a single redirected request, and no AI panels render. Append &override=1 to test the exception rule.
- Deploy Fallback Guard: Trigger a manual DOM mutation or temporarily disable the network rule to verify the content script logs a warning and applies
display: none. Re-enable the rule and confirm normal operation resumes.