
cwspy.com · July 16, 2026 · 8 min read
Manifest V3 and Ad Blockers: What You Can (and Can't) Build Anymore
No category of extension has been hit harder by Manifest V3 than ad and content blockers. If you build a Manifest V3 ad blocker, or you are trying to understand why an ad blocker you use got weaker after an update, the cause is a single API change: webRequest, the blocking, runtime-inspection API blockers used to rely on, is gone for new extensions. Its replacement, declarativeNetRequest, works on fundamentally different terms.
Why declarativeNetRequest changes what a blocker can do
Under Manifest V2, a blocker could inspect every request as it happened and decide, in code, whether to block, redirect, or modify it — arbitrary logic, evaluated live. Under Manifest V3's declarativeNetRequest, you instead register a fixed set of rules ahead of time, and Chrome itself applies them. The rules are fast and privacy-friendlier (Chrome never has to hand your extension the full content of every request), but they are also capped and static.
| Manifest V2 (webRequest) | Manifest V3 (declarativeNetRequest) |
|---|---|
| Inspect and decide on every request at runtime, with arbitrary logic | Match requests against a pre-declared, static rule set |
| Effectively unlimited custom filter logic | A capped number of rules per extension (dynamic + static combined) |
| Can rewrite response bodies and headers freely | Limited to a fixed set of declarative actions: block, redirect, modify headers |
| Rule updates apply instantly, computed on the fly | Rule sets are declared in the package or updated via the dynamic rules API, both bounded |
The practical effect: extremely aggressive, highly customized blocking — the kind that adapts filter logic per-site in real time — is harder or impossible to replicate exactly. Most mainstream ad-blocking use cases (blocklist-based filtering, cosmetic hiding) still work well; what breaks is the long tail of advanced, dynamic filtering behavior.
The caps are concrete, not vague: at the time of writing, an extension gets 30,000 dynamic and session rules combined, plus a guaranteed minimum of 30,000 static rulesfrom its own rulesets, with additional static rules available from a shared cross-extension pool if capacity allows. Google occasionally raises these numbers, so check the current values in Chrome's own declarativeNetRequest rule-limit reference before you promise a specific blocklist size in your listing.
What still works well under Manifest V3
- Standard blocklist filtering (EasyList-style rules) maps onto declarativeNetRequest rule sets reasonably directly.
- Cosmetic filtering — hiding elements via CSS/DOM manipulation — is unaffected, since it never depended on webRequest.
- Static, well-known tracker and ad-domain blocking works fine as pre-declared rules.
What is harder or lost
- Blocking decisions that depend on response content inspected at runtime.
- Extremely large, frequently updated custom rule sets bumping into rule caps.
- Per-request logic that reacts to page context rather than a static URL pattern.
If your Manifest V3 ad blocker's value proposition is "we block more than the built-in filter," test that claim against declarativeNetRequest's real limits before you promise it in your listing. A description that overclaims what the extension can now do is a support-ticket and negative-review generator, not a growth lever.
What a declarativeNetRequest rule actually looks like
A static rule is a JSON object, not code — this is the shape every blocklist entry has to compile down to:
| Field | Purpose |
|---|---|
| id | Unique number identifying the rule |
| priority | Which rule wins when more than one matches |
| action.type | "block", "redirect", "allow", or "modifyHeaders" |
| condition.urlFilter | The URL pattern to match, e.g. "||ads.example.com^" |
| condition.resourceTypes | Which request types it applies to (script, image, xmlhttprequest, etc.) |
A blocklist maintainer's job under Manifest V3 is largely converting existing filter syntax (EasyList-style ||domain.com^ patterns already map closely) into this rule shape and staying inside the caps above — not writing runtime logic anymore.
Building or migrating a blocker: what to check
- Audit your existing webRequest rules and count how many map cleanly onto declarativeNetRequest's static rule format.
- Check your rule count against the current dynamic and static rule limits — a blocklist that used to be unlimited may need to be pruned or prioritized.
- Move any content-based decisions (not just URL pattern matching) to what declarativeNetRequest's supported actions can express, or accept the capability is gone.
- Update your store listing description to reflect what the extension actually blocks now — do this before publishing the migrated build, not after users notice the gap.
- Ship, then watch reviews closely for the first two weeks; blocking-behavior regressions get reported fast and specifically by an engaged user base.
The competitive angle
Every blocker extension in a given category is going through the same migration on roughly the same timeline. That makes this a rare moment where the store's usual stability breaks: extensions that migrate cleanly and communicate the change well can pick up installs from users abandoning a competitor whose blocker got noticeably worse.