cwspy.com · July 16, 2026 · 8 min read

Manifest V3 and Ad Blockers: What You Can (and Can't) Build Anymore

declarativeNetRequest rules replacing webRequest for a Manifest V3 ad blocker

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.

Can you still build a Manifest V3 ad blocker?

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. (If your Manifest V2 blocker already went gray, start with why Chrome disabled it — this article covers what to rebuild toward.) 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 logicMatch requests against a pre-declared, static rule set
Effectively unlimited custom filter logicA capped number of rules per extension (dynamic + static combined)
Can rewrite response bodies and headers freelyLimited to a fixed set of declarative actions: block, redirect, modify headers
Rule updates apply instantly, computed on the flyRule 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 rules from its own rulesets. More static rules are 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:

FieldPurpose
idUnique number identifying the rule
priorityWhich rule wins when more than one matches
action.type"block", "redirect", "allow", or "modifyHeaders"
condition.urlFilterThe URL pattern to match, e.g. "||ads.example.com^"
condition.resourceTypesWhich 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

  1. Audit your existing webRequest rules and count how many map cleanly onto declarativeNetRequest's static rule format.
  2. 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.
  3. Move any content-based decisions (not just URL pattern matching) to what declarativeNetRequest's supported actions can express, or accept the capability is gone.
  4. 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.
  5. Ship, then watch reviews closely for the first two weeks; blocking-behavior regressions get reported fast and specifically by an engaged user base.

If you are a user, not a developer

If a blocker you rely on got worse after an update, nothing is broken on your side and reinstalling will not help — the extension is doing what the current rules allow. What is worth checking is whether the extension has settings that trade breadth for depth, since several blockers now ask you to pick which lists to enable rather than enabling everything by default. Beyond that, the honest options are to accept the reduced coverage, switch to a blocker that fits more of what you need inside the same limits, or block at a layer outside the browser entirely.

How to keep filter lists current inside the caps

The hardest part of running a blocker under the new model is not the first port, it is everything after: filter lists change constantly, and the static ruleset you shipped is frozen until your next review. Three approaches get used, and most serious blockers combine them.

  • Static rulesets for the stable core. The domains that have been serving ads for years do not need weekly updates. Put them in the package, where they cost you nothing but package size.
  • The dynamic quota for the moving parts. Newly discovered domains and user-added rules belong here, fetched and applied without a review cycle. Budget it deliberately — it is a fixed allowance, not a spare tyre.
  • Enable and disable rulesets per user. Shipping several regional or topic-specific rulesets and letting the user turn on only the ones they need keeps each individual install inside the limits without shrinking what you offer overall.

Whatever the split, write down what happens when a list overflows the cap. Silently dropping the last few thousand rules produces a blocker that works for most people and mysteriously fails for a few, which is the worst possible failure mode to debug from reviews.

Telling users what changed

Blocker users are unusually attentive and unusually vocal, and they will notice a capability regression whether or not you mention it. Saying it first costs you a paragraph; saying it never costs you the rating.

  1. Update the listing description to describe what the extension blocks now, in plain terms, before the migrated build goes live.
  2. Use the update notes for the specific changes — which capability went away, what replaced it, what the user needs to do differently, if anything.
  3. If something genuinely cannot be replicated, say so and say why. "Chrome removed the API this depended on" is an answer users accept; silence reads as neglect.
  4. Watch the review stream for the first fortnight and answer the specific complaints. A public, specific reply to a one-star review is read by everyone who lands on the listing afterwards.

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.

What breaks when you port a blocker to Manifest V3?

The migration is not a translation of one API into another, because the two models disagree about who makes the decision. Under webRequest your code saw each request and chose; under declarativeNetRequest you hand Chrome a rulebook in advance and it decides without you. Four consequences follow, and they account for most of the friction people report.

What you hadWhat you getPractical consequence
A decision per request, in JSStatic rules evaluated by the browserAnything that depended on runtime state — a counter, a heuristic, a lookup — has to become a rule or disappear
Rules updated whenever you likedStatic rulesets shipped in the package, plus a dynamic quotaA filter-list refresh means a store review, unless it fits the dynamic budget
Unlimited list size in practiceA capped number of rulesLarge community filter lists have to be trimmed or split, and the trimming is a product decision
Full visibility of what was blockedFeedback only where explicitly availableYour own diagnostics get thinner, so user reports matter more than they used to

The migration also changes what your users experience when it goes wrong. A blocker that silently stops catching something does not throw an error — it just quietly lets a page through. That failure mode reaches you as a one-star review or a silent removal rather than a bug report, which is why a departure question is worth wiring up before the port, not after: see why users uninstall Chrome extensions.