An ad blocker intercepts each outgoing request, compares the URL and its context against filter lists, and cancels the ones that match; cosmetic rules then hide the empty spaces left behind. Manifest V3 in Chrome removed the API that let extensions make that decision in code, replacing it with declarative rules under fixed size limits, which is why Chrome blockers are now less capable than Firefox ones.
Two kinds of filtering
A content blocker does two separate jobs, and it helps to keep them apart.
Network filtering stops a request before it leaves the browser. When a page tries to load https://ads.tracker.example/pixel.gif, the blocker sees the URL, the type of resource (script, image, frame, XHR), the page that requested it and whether the request is third-party, and decides whether to cancel it. A cancelled request means no ad, no cookie, no beacon and no bytes downloaded. This is the job that protects privacy, because the tracker's server never learns the page was viewed.
Cosmetic filtering runs after the page loads and hides elements using CSS selectors. It removes the empty container the ad would have filled, the "please disable your blocker" overlay and the sponsored-post cards that are served first-party and cannot be blocked at the network level. Cosmetic filtering changes what you see but does not, on its own, stop any data leaving your machine.
The tracking guide explains why the network side is the one that matters: a tracker that never receives a request has no identifier to work with.
Filter lists and their syntax
Blockers do not ship with knowledge of every ad server. They subscribe to filter lists, plain-text files maintained by volunteers and updated daily. EasyList targets ads, EasyPrivacy targets trackers and analytics, and most blockers add their own lists on top. The syntax originated in Adblock Plus and is now shared, with extensions, by uBlock Origin, AdGuard, Brave and others. A few real rules show the shape:
||doubleclick.net^
||google-analytics.com/analytics.js$script
||facebook.com/tr/$image,third-party
example.com##.sponsored-post
@@||example.com/assets/*$script,domain=example.com
Reading them: || anchors to a domain and all its subdomains; ^ is a separator so doubleclick.net does not also match doubleclick.network; $script and $third-party restrict the rule to a resource type or context; ## introduces a cosmetic rule scoped to a site; @@ is an exception that re-allows something a broader rule would block. A list of tens of thousands of these is compiled into an efficient matcher at load time, which is why a blocker adds almost no latency and usually makes pages faster.
CNAME resolution and the harder cases
The easy rule above matches a domain. Trackers responded with CNAME cloaking: a customer's own subdomain aliased to the tracker, so the URL never contains the blocked domain. uBlock Origin on Firefox resolves the alias with the browser's DNS API and blocks if the canonical name matches a rule. Chrome offers no DNS API to extensions, so Chrome blockers cannot do this; the only Chrome-side mitigation is list entries for the specific cloaked subdomains, which is a losing race.
Other hard cases are first-party analytics served from the site's own domain (blocking them breaks the site, so lists usually leave them), bundled scripts where the tracker is compiled into the same file as the page's own code, and server-side tagging where the site forwards events itself. None of these can be solved by network filtering, which is the honest limit of the tool.
What Manifest V3 changed
Chrome extensions are described by a manifest file whose version determines which APIs the extension may use. Under Manifest V2, a blocker registered a webRequest listener with the blocking option and inspected each request in JavaScript, returning {cancel: true} to stop it. That API allowed arbitrary logic: CNAME lookups, per-site counters, regular expressions of any complexity, dynamic decisions based on page state.
Manifest V3 removes blocking webRequest for ordinary extensions (it survives only for force-installed enterprise extensions) and replaces it with declarativeNetRequest. The extension no longer sees requests; it hands the browser a set of rules in advance and the browser applies them. A rule looks like this:
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "||doubleclick.net^",
"resourceTypes": ["script", "image", "sub_frame", "xmlhttprequest"]
}
}
The rules are subject to limits set by the browser. Chrome guarantees each extension at least 30,000 enabled static rules, with a global cap of 330,000 static rules shared across all installed extensions; an extension may ship up to 100 static rule sets and enable 50 at once; dynamic rules added at runtime are capped at 30,000 per extension, of which only 5,000 may be the "unsafe" kinds such as redirects; and regular-expression rules are limited to 1,000. EasyList plus EasyPrivacy alone run to well over 100,000 rules, so a Chrome blocker must trim, merge and prioritise its lists to fit, and it cannot update them without shipping a new extension version unless it spends its dynamic-rule budget.
Google finished disabling Manifest V2 extensions for Chrome users during 2024 and 2025. uBlock Origin, the most capable blocker, was turned off in Chrome as a result; its author ships uBlock Origin Lite, a declarativeNetRequest version that is explicitly less capable. Mozilla adopted Manifest V3 too but kept blocking webRequest, so full uBlock Origin continues to work in Firefox. Safari uses its own declarative content blocker API with a rule cap per blocker, which is why Safari blockers ship as several separate rule sets.
| Capability | Chrome (MV3) | Firefox (MV3) | Safari |
|---|---|---|---|
| Per-request logic in code | No | Yes | No |
| CNAME uncloaking | No | Yes | No |
| Rule count | 30,000 guaranteed, 330,000 global cap | No hard cap | Per-blocker cap, multiple blockers allowed |
| Cosmetic filtering | Yes, via content scripts | Yes | Yes, limited |
| List updates without extension update | Only via dynamic rules | Yes | Yes, via app |
Our view: Manifest V3 has genuine security benefits, since an extension that cannot read every request also cannot exfiltrate it, and the extension safety guide explains why that risk is real. But the rule limits were set by the browser vendor that also runs the largest ad network, and the practical outcome is that the best blocking now lives in Firefox. If blocking quality is your priority, that is the browser to use. If you stay in Chrome, choose a blocker built for declarativeNetRequest from the start rather than a port, and accept the gap.
Is a blocker an extension risk?
A blocker needs to see or rewrite every request, which is the broadest permission an extension can hold. That is acceptable for an open-source blocker with a public list ecosystem and years of scrutiny; it is not acceptable for an unknown blocker with a similar name, several of which have been caught injecting their own ads or selling browsing data. Before trusting one, run it through the extension analyzer and look at the host permissions and external endpoints it declares. A legitimate blocker contacts list servers and nothing else. Our own Scan.now extension combines a tracker blocker with automatic cookie deletion for this reason: the two protections need the same permission, and one audited extension is safer than two.
What a blocker cannot protect you from
- First-party tracking. The site you are on sees everything you do there, and its own analytics cannot be blocked without breaking it.
- Fingerprinting by allowed scripts. A blocker stops known fingerprinting libraries, but any script that is permitted to run can read canvas, fonts and hardware. Check your fingerprint uniqueness separately.
- Cookies that arrive anyway. Blocking a tracker's script does not delete the cookie it set last month. That is the job of automatic cookie deletion.
- Malicious ads on allowed networks. Malvertising rides on the ad networks the lists allow or the site serves itself.
- Phishing and malware. A blocker is not a security product. A link in an email is checked by the phishing URL checker, not by a filter list.
Recommended configuration
- Use one blocker. Two blockers with overlapping lists double the work, break exception rules and add fingerprint surface.
- Keep the default lists: EasyList, EasyPrivacy and the blocker's own. Add a regional list if you read sites in another language.
- Do not add dozens of extra lists; each one increases false positives and, in Chrome, eats into the rule budget.
- Allowlist the sites you want to support by domain rather than disabling the blocker globally.
- Re-check the privacy hub for the other layers, because the blocker is necessary and not sufficient.