Skip to main content
Build an adblocker, also written ad blocker or content blocker, with the declarativeNetRequest API. You declare matching rules, and the browser blocks requests natively before they leave the network stack.

What you build

Why MV3 replaced blocking webRequest

Manifest V2 ad blockers registered blocking webRequest listeners. Every request paused while extension JavaScript decided its fate. Manifest V3 removed that model for performance and privacy reasons. With declarativeNetRequest, the browser evaluates your rules itself. Your code never sits on the request path and never reads request contents to block them. For proxy-style use cases, such as routing all traffic through another server, use the proxy API instead of request rules.

Manifest

Block and allow rules need no host permissions. Redirect rules and header rules need host permissions for the affected sites.
manifest.json
Extension.js compiles the ruleset file that rule_resources references and emits it with the build output.

Static rules

Static rules live in rules.json and load when the extension loads.
rules.json
The ||domain^ filter syntax matches a domain and all of its subdomains.

Block a whole page

A rule that names main_frame in resourceTypes blocks the top-level navigation, not only the subresources:
rules.json
The two browsers show different results, and both mean that the rule worked:
  • Chrome replaces the tab with its own blocked page. It reads “This page has been blocked by an extension” with the code ERR_BLOCKED_BY_CLIENT. Nothing is logged to the console.
  • Firefox cancels the navigation, and the tab keeps the page that it had before. There is no error page and no console entry.
Extension.js copies the rule file to dist/<browser>/declarative_net_request/ and rewrites the manifest path, so reference it the same way the manifest above does.
The behavior in this section and the next was measured on Chrome 155 and Firefox 158 with Extension.js 4.1.33.

Redirect a blocked page to a page in the extension

Replace the block action with a redirect to a page that ships with the extension:
rules.json
Put the page at public/blocked.html. Extension.js copies public/ to the output root, so /blocked.html resolves with no manifest entry for the page. A redirect needs more permission than a block, and the two browsers differ:
  • Chrome needs a host permission that covers the site. Without it the rule is a silent no-op: the page loads normally, the ruleset still reports enabled, and nothing reaches the console. Chrome does not need web_accessible_resources for a main_frame redirect to an extension page.
  • Firefox needs the host permission and the page listed in web_accessible_resources. Without the web_accessible_resources entry the navigation fails with NS_ERROR_DOM_BAD_URI and the tab stays where it was. Without the host permission it is a silent no-op, like Chrome.
The action template builds Firefox as Manifest V2, where host permissions live in permissions and web_accessible_resources is a list of strings. A prefixed key replaces the plain key on its target, so the Firefox list repeats declarativeNetRequest. One manifest covers both browsers with browser-specific fields:
manifest.json
If your project builds Firefox as Manifest V3 as well, drop the firefox: keys and use the object form, [{ "resources": ["blocked.html"], "matches": ["<all_urls>"] }], under plain web_accessible_resources. Both browsers accept it.

Dynamic rules

Use dynamic rules for filters that change at runtime, such as user-added blocklist entries. Remove a rule id before you re-add it, so updates stay idempotent.
background.js
Chrome caps static and dynamic rule counts. Check the current limits in the declarativeNetRequest reference before you ship large filter lists.

Count blocked requests on the badge

One call turns the action badge into a per-tab counter of matched rules.
background.js
To read matched rules yourself with getMatchedRules, add the declarativeNetRequestFeedback permission.

Run it

Before you edit manifest.json, know that manifest changes need a dev-server restart. See Dev update behavior.
Open a page that requests a blocked domain. The badge count rises as rules match.

Firefox notes

  • Firefox supports declarativeNetRequest in Manifest V3.
  • Firefox does not support setExtensionActionOptions, so the badge counter is Chromium-only.
  • Firefox still allows blocking webRequest in Manifest V3, which Chrome reserves for policy installs.
  • Use browser-specific manifest fields when the two targets diverge.

Start from a template

The action template ships a background script plus a toolbar popup, a good base for a blocker UI.
Repository: extension-js/examples/action

Best practices

  • Keep static rules in rules.json and reserve dynamic rules for user choices.
  • Give every dynamic rule a stable id, so removals stay predictable.
  • Scope resourceTypes to what you block, not to every type.
  • Prefer block and allow rules, which need no host permissions.
  • Test rules against real pages before you publish filter updates.

Next steps