TL;DR: Most extensions block Google's AI Overviews by hiding the panel with a content script after it renders — fragile, flickery, and always a step behind Google's markup changes. A better approach: force
udm=14at the network layer withdeclarativeNetRequest, so the AI Overview never loads. The content script becomes a backstop, not the main mechanism. One Chrome API mystery — AI Mode being invisible to four different extension APIs — shows why the DOM was never the right layer.
Google puts an AI Overview at the top of most search results now, and a lot of people would rather it didn't. So there's a whole shelf of Chrome extensions that remove it. Almost all of them work the same way, and I think that way is a mistake.
The obvious approach, and why it's a trap
The default move is DOM-hiding: inject a content script, wait for the AI Overview panel to render, find it by class name or attribute, and set display: none. It's the first thing anyone reaches for, and it works — until it doesn't.
The problems are all baked into the approach. You're reacting after the render, so there's a flash of AI content before your script catches it. You're matching against Google's markup, which is obfuscated and reshuffled constantly, so every layout change is a silent breakage. And you're paying for DOM churn on a page you don't control. You end up in a permanent game of catch-up against a page that changes whenever Google feels like it.
The deeper issue is that you're operating one layer too high. The panel is a symptom. By the time it's in the DOM, the work is already done — the server decided to send it, the page rendered it, and now you're scrambling to un-render it. If you can move the decision earlier, none of that scramble has to happen.
The thesis: prevent it at the network layer
Google Search takes a parameter, udm, that selects which result vertical you get. udm=14 is the plain "Web" results view — the classic list of links, no AI Overview, no AI Mode. It's Google's own filter; we're just always asking for it.
So instead of hiding the panel after it loads, force udm=14 onto the search request before it loads. The AI Overview is never generated, never sent, never rendered. Nothing to hide because nothing arrived.
Manifest V3's declarativeNetRequest does exactly this — it rewrites requests by rule, without the extension ever reading the traffic. Here's the core rule:
{
id: 1,
priority: 1,
action: {
type: "redirect",
redirect: {
transform: {
queryTransform: {
addOrReplaceParams: [{ key: "udm", value: "14" }]
}
}
}
},
condition: {
requestDomains: ["www.google.com"],
urlFilter: "/search?",
requestMethods: ["get"],
resourceTypes: ["main_frame"]
}
}
That's the whole mechanism. Every top-level search navigation gets udm=14 stamped onto it and comes back as clean Web results. No content script races the render, because there's no AI render to race. It's language-independent too — udm is a server-side parameter, so it doesn't care whether Google is serving you English or German, which is a nice bonus over text-matching a panel heading.
Note resourceTypes: ["main_frame"] — this only touches the top-level page load, not the page's own internal requests, so it changes what you searched for without breaking how the page works.
The proof that the DOM was never enough: AI Mode
Here's the part that turned me from "the network layer is nicer" to "the DOM layer is genuinely the wrong place."
Alongside AI Overviews, Google has AI Mode — a separate conversational surface you reach from a tab in the results. I wanted to suppress its page too, and I assumed the content-script backstop would catch it like anything else. It didn't. So I tried to observe that page from every angle a Chrome extension has:
-
declarativeNetRequest— no rule matched the navigation. - A
MutationObserveron the document — fired for nothing. -
chrome.tabs.onUpdated— never reported the change. -
chrome.storage.onChangedas a last resort signal — silent.
Four unrelated APIs, and every one came back blank — even though the address bar plainly showed udm=50 for AI Mode. Not "my selector missed it." Zero signal of any kind reached the extension.
The consistent silence across four different mechanisms is the tell: that surface isn't part of the same page a content script attaches to. Whatever it is, it's stitched into the tab in a way that never injects a content script or surfaces a navigation the extension can see. Which means the DOM layer can't touch it even in principle. If your entire strategy is "hide it in the DOM," you have no move here at all.
The network-layer approach at least has an answer: since I can't catch that page, I prevent the click that leads to it — the extension hides the AI Mode tab in the results before it's ever pressed. Prevention again, one step earlier than the thing I couldn't reach.
What the network layer can't do either
I'd be selling you something if I stopped there, so here are the honest limits.
Forcing udm=14 catches the searches that go through the search box. It does not catch someone who navigates directly to a udm=50 URL — a typed address, an old bookmark, an external link — because there's no clean search request to rewrite. And it does nothing about Chrome's own omnibox AI button, which is native browser UI, not part of any page; no extension API can remove it.
Those aren't bugs to fix later; they're the edge of what this layer owns. Naming them is the difference between an approach you trust and one you find out about the hard way.
Where the content script goes: the backstop, not the star
I didn't delete the content script — I demoted it. It still runs, but as a labeled safety net for one specific case: Google quietly changing how udm=14 behaves. If that ever happens, the backstop hides whatever slipped through and the extension tells me about it, instead of silently degrading.
That's the content script in its right role — a fallback for the thing the primary mechanism can't guarantee, not the primary mechanism itself. The same code that's fragile as a front line is perfectly reasonable as a backstop, because it only has to fire when something has already gone wrong.
There's a small elaboration worth mentioning: I wanted users to be able to summon the AI Overview for a single search on demand. That's a second declarativeNetRequest rule at higher priority that allows a specifically-marked request through unmodified — the redirect and the exception live at the same layer, which keeps the whole thing coherent.
The transferable part
Forget Google for a second. The lesson generalizes: when you're fighting a web page's behavior, find the layer that actually owns the decision before you reach for the DOM. The DOM is where behavior becomes visible, which makes it the tempting place to intervene — and often the wrong one, because by then the decision is already made. Ask what produced the thing you're trying to stop, and see if you can get upstream of it. Sometimes you can't, and the DOM really is your only handle. But it's worth checking, because when you can move upstream, the fragile parts of the problem tend to disappear rather than get patched.
For this problem, that meant a request-rewrite rule instead of a render-and-hide loop — and an AI Mode mystery that made the point better than I could have on purpose.
I built this out as a small extension, No AI Search, if you want to see the whole thing shipped. Source is on GitHub.
Top comments (0)