Back to articles

declarativeNetRequest in browser extensions: what a peer-reviewed study says about MV3 ad blocking

A Goethe University study found MV3 ad blockers match MV2 effectiveness. Here's what declarativeNetRequest means for developers building content filters.

Maxim Kosterin
8 min read

If you build browser extensions that touch network requests — ad blockers, privacy tools, content filters, parental controls — you've probably spent the last two years worrying about Manifest V3 killing your product. The webRequest blocking API was your Swiss army knife, and Google replaced it with declarativeNetRequest, a declarative system with hard rule limits and no dynamic inspection.

The internet consensus? MV3 destroys ad blockers. Google did this on purpose. Privacy tools are dead.

Except a peer-reviewed study just proved that's not what's happening.

The study that changes the conversation

Researchers Karlo Lukic and Lazaros Papadopoulos at Goethe University Frankfurt published a paper in Proceedings on Privacy Enhancing Technologies (PoPETs) 2026 that directly tested whether MV3 ad blockers are worse than their MV2 predecessors. Their conclusion: no statistically significant reduction in effectiveness.

Let that sink in. This isn't a blog post or a Twitter thread. It's an independently funded, peer-reviewed study from a respected privacy research venue — unaffiliated with Google or any ad blocker vendor.

They tested four widely used ad blockers: Adblock Plus, AdGuard, Stands, and uBlock Origin. The MV2 versions used the synchronous chrome.webRequest API. The MV3 versions used the asynchronous chrome.declarativeNetRequest API. Both ran with default filter sets to replicate what a typical user would experience.

The result? MV3 versions actually blocked slightly more trackers — an average of 1.8 additional tracking scripts per website.

Why this matters if you're building extensions

If you're an extension developer who's been holding off on migrating to MV3, this study should recalibrate your priorities. The data suggests your users won't notice a downgrade in filtering quality — if you've done the migration work properly.

But "properly" is the key word here. The declarativeNetRequest API is fundamentally different from webRequest, and understanding those differences is what separates a clean migration from a broken one.

The old way: webRequest

With MV2, you could intercept any network request in real time, inspect its headers, modify or block it on the fly, and make decisions based on dynamic state. Here's a simplified pattern:

chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    if (shouldBlock(details.url, details.type)) {
      return { cancel: true };
    }
  },
  { urls: ["<all_urls>"] },
  ["blocking"]
);

This was powerful. It was also a security and performance concern — any extension with webRequest blocking could silently intercept and modify every single HTTP request your browser made.

The new way: declarativeNetRequest

MV3's declarativeNetRequest (DNR) flips the model. Instead of running your code on every request, you declare rules upfront and the browser engine evaluates them natively. You never see the actual request data:

// In your rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": { "type": "block" },
    "condition": {
      "urlFilter": "||tracker.example.com",
      "resourceTypes": ["script", "image", "xmlhttprequest"]
    }
  }
]

And in your manifest:

{
  "declarative_net_request": {
    "rule_resources": [{
      "id": "blocklist",
      "enabled": true,
      "path": "rules.json"
    }]
  }
}

The browser handles the matching. Your extension never touches request data. That's better for user privacy and browser performance — but it means you lose the ability to make context-aware decisions at runtime.

The rule limit problem (and why it's smaller than you think)

The biggest complaint about DNR has been the rule limit. When declarativeNetRequest first launched, the static rule cap was 30,000 rules. For context, uBlock Origin's default filter lists contain well over 300,000 rules. That's a ten-to-one gap.

Google heard the feedback. The guaranteed minimum for static rules is now 330,000, with browsers often allowing more depending on available memory. Dynamic and session rules have separate limits (check chrome.declarativeNetRequest.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES for the current value in your target browser).

Here's the thing: most extensions aren't uBlock Origin. If you're building a corporate content filter, a parental control tool, or even a moderate ad blocker, you're probably working with thousands of rules, not hundreds of thousands. The 330,000 limit is generous for the vast majority of use cases.

And even for massive filter lists, the Goethe study shows that well-optimized rule sets under DNR perform just as well as the old approach. The trick is in the optimization, not in the API surface area.

Practical migration patterns for DNR

If you're ready to move from webRequest to declarativeNetRequest, here are the patterns I've seen work best.

Static rules for known filter lists

If your blocking logic is based on domain lists or URL patterns that don't change at runtime, static rulesets are your primary tool. Define them in JSON, reference them in your manifest, and let the browser handle matching:

// manifest.json
{
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [
      {
        "id": "ads",
        "enabled": true,
        "path": "rules/ads.json"
      },
      {
        "id": "trackers",
        "enabled": true,
        "path": "rules/trackers.json"
      }
    ]
  }
}

Split your rules into logical rulesets. Users can toggle categories on and off, and you stay under per-ruleset limits more easily.

Dynamic rules for user customization

Users want to add custom block rules or allowlist specific sites. That's where dynamic rules come in:

async function addUserBlockRule(domain) {
  const currentRules = await chrome.declarativeNetRequest.getDynamicRules();
  const nextId = currentRules.length > 0
    ? Math.max(...currentRules.map(r => r.id)) + 1
    : 1;
 
  await chrome.declarativeNetRequest.updateDynamicRules({
    addRules: [{
      id: nextId,
      priority: 2,
      action: { type: "block" },
      condition: {
        urlFilter: `||${domain}`,
        resourceTypes: [
          "script", "image", "stylesheet",
          "xmlhttprequest", "sub_frame"
        ]
      }
    }]
  });
}

Dynamic rules persist across browser sessions but count against a separate, smaller limit. Use them for user-specific customizations, not your core filter list.

Session rules for temporary state

Need to block something temporarily — during a browsing session, for a specific tab, or based on a toggle? Session rules disappear when the browser closes:

await chrome.declarativeNetRequest.updateSessionRules({
  addRules: [{
    id: 9999,
    priority: 3,
    action: { type: "block" },
    condition: {
      urlFilter: "||ads.tempsite.com",
      resourceTypes: ["script"],
      tabIds: [activeTabId]
    }
  }]
});

This is useful for "block this element" features or per-tab configurations. No cleanup needed — the browser handles it.

The caveats the study didn't cover

The Goethe study is solid, but it has acknowledged limitations that matter for developers.

First, they used default filter sets. If your extension relies on advanced, regularly updated filter lists with hundreds of thousands of rules, the DNR rule limits might still bite. The study didn't test whether less popular or long-tail websites get worse filtering under DNR constraints.

Second, this is a snapshot. As the researchers note, Chrome could tighten or loosen MV3 constraints in future releases. Building your extension architecture to handle rule limit changes — with graceful degradation for less critical rules — is worth the engineering effort now.

Third, uBlock Origin's full version is no longer available on Chrome. Raymond Hill maintains uBlock Origin Lite (MV3) for Chromium browsers, but the full-featured version lives on Firefox. If your extension has similar complexity, plan for Firefox as a first-class target, not an afterthought.

What to do right now

If you're an extension developer working with network request filtering, here's my recommended sequence:

  1. Audit your current rule count. Export your filter lists and count the rules. If you're under 100,000, the DNR migration is straightforward. If you're above 300,000, you'll need to prioritize and segment your rules.

  2. Split rules into logical rulesets. Categories like "ads," "trackers," "social widgets," and "custom" map cleanly to separate ruleset files. This gives users control and keeps you under per-ruleset limits.

  3. Use dynamic rules sparingly. Reserve them for user-generated content — custom blocks, allowlists, temporary overrides. Don't use dynamic rules for your core filter list.

  4. Test on real websites, not synthetic benchmarks. The Goethe study's strength was testing on actual ad-supported websites. Your QA should mirror that approach.

  5. Track your extension's performance on Extenshi's catalog. The security and privacy scoring can help you understand how your permissions profile compares to competitors — and what your users see before they install.

  6. Validate the manifest and scan the build before you upload. My free Manifest V3 generator checks your declarative_net_request block against store policy, and @extenshi/cli (npx @extenshi/cli) runs a pre-publish security scan straight from your terminal or CI pipeline. You get 5 scans and 25 reads a month free; prepaid credit packs cover you past that.

The MV3 migration isn't optional anymore

With Manifest V2 fully disabled in Chrome 138 and complete removal coming in Chrome 139 by June 2026, this isn't a theoretical exercise. If your extension still uses webRequest blocking on Chrome, you're on borrowed time.

The good news from the Goethe study is that the migration doesn't have to mean a worse product. Your MV3 extension can match — and according to the data, occasionally beat — your MV2 version's filtering effectiveness. The declarativeNetRequest API is different, not worse. It just requires a different way of thinking about network filtering.

Stop treating MV3 as a loss and start treating it as a different architecture. The research backs you up.

Want to see how your extension stacks up? Search the Extenshi catalog to see your extension's security score, permission breakdown, and how it compares to alternatives in your category.


This article is based on publicly available security research and news reporting. Extenshi does not independently verify all claims made by third-party researchers. References to specific companies or products reflect the findings of cited sources and do not constitute accusations of intentional wrongdoing. If you believe any information is inaccurate, please contact us at [email protected].

Related Articles