Allowlisting Sites with allow and allowAllRequests Rules
Build a site allowlist for an MV3 blocker: allow versus allowAllRequests, main_frame and sub_frame scoping, priorities that beat block rules, initiatorDomains, and syncing the list across devices.
Table of Contents
Every blocker needs an allowlist: “don’t block anything on my bank”, “this news site breaks with blocking on, pause it”, “always allow requests to this CDN”. With declarativeNetRequest the allowlist is just more rules — but which rule? An allow rule on the site’s domain lets the page’s own requests through while still blocking the third-party ads it loads. An allowAllRequests rule on the page’s frame lets everything that page loads through, which is what users usually mean. Mixing them up produces allowlists that half-work, and getting the priorities wrong produces allowlists that do not work at all. This guide builds one correctly. It belongs to declarativeNetRequest rules.
allow versus allowAllRequests
An allow rule matches individual requests: if a request matches it, that request is not blocked or redirected by lower-priority rules. Its condition describes the request — its URL, domain, resource type. An allowAllRequests rule matches frames: it applies only to main_frame and sub_frame requests, and when one matches, every request made by that frame and its descendant frames is allowed (subject to priority). Its condition describes the document being loaded. So “allow requests to cdn.example.com” is an allow rule with requestDomains: ["cdn.example.com"], while “turn off blocking on news.example” is an allowAllRequests rule with requestDomains: ["news.example"] and resourceTypes: ["main_frame"].
Step-by-step: a working site allowlist
1. Choose a priority band above every block rule
1// rules/priorities.js
2export const PRIORITY = Object.freeze({
3 listBlock: 1, // static filter lists
4 userBlock: 2, // user-added blocks
5 listException: 3, // exceptions from filter lists (@@ rules)
6 userAllow: 50, // user's allowed resources
7 siteAllow: 100, // user's allowlisted sites — must win over everything
8});
Execution context: a shared constants module. When rules of different priorities match the same request, the highest priority wins; actions only break ties at equal priority. A site allowlist must sit above every block rule, including any added later, so give it a wide margin. Recording the bands in one place stops a future rule from accidentally outranking the allowlist.
2. Allowlist a whole site with allowAllRequests
1// sw.js
2export async function allowSite(hostname) {
3 const id = await ruleIdFor(`site:${hostname}`);
4 await chrome.declarativeNetRequest.updateDynamicRules({
5 removeRuleIds: [id],
6 addRules: [{
7 id,
8 priority: PRIORITY.siteAllow,
9 action: { type: "allowAllRequests" },
10 condition: { requestDomains: [hostname], resourceTypes: ["main_frame"] },
11 }],
12 });
13}
Execution context: the service worker. requestDomains matches the domain and its subdomains, so example.com covers www.example.com. Matching main_frame means the rule triggers when the user navigates to the site; from then on, every request the page makes — including to ad networks — is allowed. Removing the rule id first makes the call idempotent.
3. Cover embedded frames deliberately
1condition: { requestDomains: [hostname], resourceTypes: ["main_frame", "sub_frame"] }
Execution context: the same rule with sub_frame added. With sub_frame, the allowlisted site is also unblocked when it appears as an iframe on other sites — an embedded video player, for example. Whether users want that depends on the feature; for “pause on this site”, main_frame alone is usually correct, because the user is thinking about the site in the address bar.
4. Allow specific resources everywhere with allow
1export async function allowResourceDomain(domain) {
2 const id = await ruleIdFor(`res:${domain}`);
3 await chrome.declarativeNetRequest.updateDynamicRules({
4 removeRuleIds: [id],
5 addRules: [{
6 id,
7 priority: PRIORITY.userAllow,
8 action: { type: "allow" },
9 condition: { requestDomains: [domain] },
10 }],
11 });
12}
Execution context: the service worker. This is the fix for “the extension blocks the comment widget on every site”: an allow for the widget’s domain lets its requests through wherever they appear, while blocking stays active for everything else. Narrow it with initiatorDomains if the allowance should apply only when the request comes from certain sites.
5. Keep the allowlist as data and sync it
1// storage is the source of truth; rules are derived from it
2export async function setAllowlist(hostnames) {
3 await chrome.storage.sync.set({ allowlist: [...new Set(hostnames)].sort() });
4}
5
6chrome.storage.onChanged.addListener(async (changes, area) => {
7 if (area !== "sync" || !changes.allowlist) return;
8 await rebuildAllowRules(changes.allowlist.newValue ?? []);
9});
Execution context: the service worker. Storing the hostnames in chrome.storage.sync makes the allowlist follow the user to other devices; deriving rules from it on every change — and on onInstalled — keeps rules and data consistent. Dynamic rules themselves do not sync. Keep each hostname short and the list small: sync has a 100 KB total quota.
6. Rebuild rules from the list atomically
1async function rebuildAllowRules(hostnames) {
2 const existing = (await chrome.declarativeNetRequest.getDynamicRules())
3 .filter((r) => r.priority === PRIORITY.siteAllow);
4 await chrome.declarativeNetRequest.updateDynamicRules({
5 removeRuleIds: existing.map((r) => r.id),
6 addRules: hostnames.map((h, i) => ({
7 id: 500_000 + i,
8 priority: PRIORITY.siteAllow,
9 action: { type: "allowAllRequests" },
10 condition: { requestDomains: [h], resourceTypes: ["main_frame"] },
11 })),
12 });
13}
Execution context: the service worker. One updateDynamicRules call that removes the old set and adds the new set is applied atomically, so there is no moment where the allowlist is empty. Reserving an id range for allowlist rules keeps them from colliding with user block rules. For large allowlists, a single rule with many requestDomains is more economical than one rule per site.
7. Show allowlist state in the popup
The popup should say clearly whether blocking is active on the current site and offer one button to toggle it. Read the allowlist from storage (not from the rules) and the tab’s hostname from the active tab; after toggling, reload the tab so the frame-level decision is re-made with the new rules.
Common mistakes
- Using
allowto pause a site. Third-party requests from the page are still blocked. - Allowlist priority too low. Equal-priority block and allow rules resolve by action order, which is fragile; outrank decisively.
allowAllRequestswith resource types other than frames. The rule is invalid; it only acceptsmain_frameandsub_frame.- Not reloading the tab. Frame-level decisions are made at navigation; changes apply on the next load.
- Syncing rules instead of data. Dynamic rules are per profile; sync the hostnames and rebuild.
Cross-browser variation
- Chrome / Edge:
allowandallowAllRequestsas described; priorities and action ordering per the DNR specification. - Firefox: supports both actions in DNR; Firefox also runs blocking
webRequestlisteners, which an allowlist built only in DNR does not affect — keep both mechanisms consistent if you use both. - Safari: supports both actions; test allowlisting with Safari’s own content blocking features disabled to avoid confusing interactions.
Verification
- Allowlist a site and reload it: requests that were blocked now succeed in DevTools’ Network panel.
- Visit another site and confirm blocking is still active there.
- Remove the site and reload: blocking resumes.
- Change the allowlist on a second synced device and confirm rules rebuild on the first after sync.
FAQ
Can I allowlist a single page rather than a site?
Yes — use urlFilter with the page’s path and resourceTypes: ["main_frame"] in an allowAllRequests rule.
Does allowAllRequests affect other extensions?
No. Each extension’s rules apply to its own decisions; another blocker evaluates its own rules independently.
Why is a request still blocked on an allowlisted site?
It may come from a frame that was not covered (a sub frame from another origin) or a rule with a higher priority. Use DNR feedback in an unpacked build to see which rule matched.
Related
- Rule priority and action precedence explained — the resolution rules in full.
- Building a content blocker with declarativeNetRequest — where the allowlist fits.
- Syncing options across a user’s devices — syncing the list itself.
- declarativeNetRequest rules — the parent topic.