@adguard/dnr-converter
v1.1.0
Published
A converter that transforms adblock-style filtering rules into rules compatible with the Declarative Net Request (DNR) API.
Downloads
462
Readme
DNR Converter
A TypeScript library that converts adblock-style filtering rules into rules compatible with Chrome's Declarative Net Request (DNR) API. It is designed for developers building Manifest V3 browser extensions that need to translate existing AdGuard or other adblock filter lists into the DNR format required by modern Chrome extensions.
Key concepts
- Filter — an adblock filter list represented as an object with an
idand textcontent. Each filter list (e.g. AdGuard Base, EasyList) becomes oneFilterinput. - Declarative rule — a
chrome.declarativeNetRequest.Ruleobject that the browser evaluates natively. The converter produces these from filter text. - Source map — a mapping from each generated declarative rule back to its originating filter and rule index, enabling reverse lookup when a declarative rule fires.
- Rule safety — Chrome classifies DNR rules as safe (block, allow, allowAllRequests, upgradeScheme) or unsafe (redirect, modifyHeaders). The library exposes a helper to check safety.
- Converter options — limits and paths that control conversion output (maximum rule counts, resource paths for redirects).
Supported rule types
The converter supports virtually all adblock network rule modifiers. The known MV3 limitations (inherent to the Declarative Net Request API) are:
Supported modifiers (with MV3 notes):
- Basic blocking/allowing rules,
$third-party,$domain(no regexps /.*TLDs), all content types ($script,$image,$stylesheet, etc.) $important,$match-case,$method,$to,$denyallow,$header(no regex values),$all$redirect— allowlist rules not supported$csp,$removeparam,$removeheader,$permissions— allowlist rules not supported; rules with identical conditions are combined only within the same filter, not across filters$urltransform— limited; complex transforms produce multiple declarative rules via pipeline stages$cookie— limited; only supported without parameters (bare$cookie)$badfilter— partial: does not handle$domainintersections correctly
Not yet supported / not convertible to DNR:
$popup,$redirect-rule,$referrerpolicy— not yet implemented$replace,$jsonprune,$hls,$network, DNS modifiers ($client,$dnsrewrite,$dnstype,$ctag) — not expressible in DNR; produce conversion errors and are skipped- Exception modifiers
$jsinject,$stealth,$urlblock,$genericblock— not yet implemented $webrtc— deprecated and not supported
For detailed conversion examples with output, see Conversion Examples.
Installation
# pnpm
pnpm install @adguard/dnr-converter
# yarn
yarn add @adguard/dnr-converter
# npm
npm install @adguard/dnr-converterPeer dependencies
The package requires @adguard/re2-wasm
(1.2.0) as a peer dependency for regex validation.
API overview
The library uses a single FilterConverter class with two conversion modes
selected by the withSourceMap option:
| | Simple mode (default) | Advanced mode (withSourceMap: true) |
| --- | --- | --- |
| Converter | FilterConverter | FilterConverter |
| Ruleset | Ruleset (sync, in-memory) | RulesetWithSourceMap (lazy-load, source map) |
| Input filter | IFilter | IFilter |
| Use case | When you only need DeclarativeRule[] output from plain filter text | When you need source maps, $badfilter cross-filter application, and serialization |
IFilter
import type { IFilter } from '@adguard/dnr-converter';Single interface used by both conversion flows:
| Method | Type | Description |
| --- | --- | --- |
| getId() | number | Unique filter identifier |
| getContent() | Promise<string> | Returns the full text of the filter list |
| getRuleByIndex(index) | Promise<string> | Returns original rule text by character offset |
| unloadContent() | void | Releases loaded content from memory |
Filter
The single concrete IFilter implementation shipped by the library supports two
construction modes:
Pre-loaded (
new Filter(id, content: string)) — accepts the filter text directly as a string.getContent()resolves immediately;unloadContent()is a no-op. Use for simple conversion or when content is already in memory.Lazy-loaded (
new Filter(id, source: () => Promise<string>)) — accepts an async callback that fetches the content on demand. Supports promise deduplication across concurrent callers, andunloadContent()releases the cached content. Use withwithSourceMap: true(advanced mode).
import { Filter } from '@adguard/dnr-converter';
// Pre-loaded (simple mode)
const filter = new Filter(1, '||example.com^');
// Lazy-loaded (advanced mode with withSourceMap: true)
const lazyFilter = new Filter(1, async () => fetchFilterText());ConverterOptions
import type { ConverterOptions } from '@adguard/dnr-converter';Configuration for the conversion process:
| Property | Type | Description |
| --- | --- | --- |
| resourcesPath | string? | Path to web-accessible resources relative to the extension root (starts with /, no trailing /). Required for $redirect rules. |
| maxNumberOfRules | number? | Maximum total declarative rules to produce. Excess rules are trimmed. |
| maxNumberOfUnsafeRules | number? | Maximum unsafe (dynamic) rules allowed. |
| maxNumberOfRegexpRules | number? | Maximum rules using regexFilter. |
| combine | boolean? | Merge all input filters into a single combined rule set. |
| withSourceMap | boolean? | When true, returns RulesetWithSourceMap instead of Ruleset. Enables source maps, $badfilter cross-filter support, and lazy loading. |
| badFilterRules | Rule[]? | Static $badfilter rules to apply at scan time (withSourceMap: true only). |
Simple flow: FilterConverter + Ruleset
Use this flow when you only need DeclarativeRule[] output from plain filter
text, without source maps or lazy loading.
import { FilterConverter, Filter } from '@adguard/dnr-converter';
const filter = new Filter(1, '||example.com^\n@@||example.com/path^');
const converter = new FilterConverter();
const [{ ruleset, errors, limitations }] = await converter.convert([filter]);
console.log(ruleset.getDeclarativeRules()); // DeclarativeRule[]
console.log(ruleset.getSafeRulesCount()); // 2
console.log(errors.length); // 0Combine multiple filters into one ruleset:
const [{ ruleset }] = await converter.convert(
[filter1, filter2],
{ combine: true },
);
// ruleset.getId() === FilterConverter.COMBINED_RULESET_IDSerialize and restore:
const json = ruleset.serialize(); // JSON string of DeclarativeRule[]
const { Ruleset } = await import('@adguard/dnr-converter');
const restored = Ruleset.deserialize(ruleset.getId(), json);Ruleset (returned by default, without withSourceMap) implements IRuleset:
| Method | Returns | Description |
| --- | --- | --- |
| getId() | string | Rule set identifier (e.g. "ruleset_1") |
| getSafeRulesCount() | number | Count of safe declarative rules (excludes unsafe) |
| getUnsafeRulesCount() | number | Count of unsafe rules |
| getRegexpRulesCount() | number | Count of regexp-based rules |
| getDeclarativeRules() | DeclarativeRule[] | All converted DNR rules (synchronous) |
| serialize() | string | JSON serialization of declarative rules |
| Ruleset.deserialize(id, json) | Ruleset | Static: reconstruct from serialized JSON |
Advanced flow: FilterConverter with withSourceMap: true + RulesetWithSourceMap
Use this mode in browser extension internals when you need source maps,
$badfilter cross-filter application, serialization with hash maps, and lazy
content loading. Pass withSourceMap: true in the options to get
RulesetWithSourceMap results.
import { Filter, FilterConverter } from '@adguard/dnr-converter';
const filter = new Filter(1, async () => '||example.com^\n@@||example.com/path^');
const converter = new FilterConverter();
const [{ ruleset, errors }] = await converter.convert([filter], { withSourceMap: true });
const declarativeRules = await ruleset.getDeclarativeRules(); // Promise<DeclarativeRule[]>
const sources = await ruleset.getRulesById(declarativeRules[0].id);
console.log(sources[0].sourceRule); // '||example.com^'Apply $badfilter from dynamic filters against static rule sets:
const [{ ruleset: dynamicRuleset }] = await converter.convert(
[dynamicFilter],
{ withSourceMap: true },
);
const rulesToDisable = await converter.computeRulesToDisable(
[dynamicRuleset],
[staticRuleset],
);
// rulesToDisable: UpdateStaticRulesOptions[]RulesetWithSourceMap (returned when withSourceMap: true) implements
IRulesetWithSourceMap:
| Method | Returns | Description |
| --- | --- | --- |
| getId() | string | Rule set identifier |
| getSafeRulesCount() | number | Count of safe declarative rules (excludes unsafe) |
| getUnsafeRulesCount() | number | Count of unsafe rules |
| getRegexpRulesCount() | number | Count of regexp-based rules |
| getDeclarativeRules() | Promise<DeclarativeRule[]> | All converted DNR rules (lazy) |
| getUnsafeRules() | Promise<DeclarativeRule[]> | Unsafe rules subset (lazy) |
| getRulesById(id) | Promise<SourceRuleAndFilterId[]> | Source rules for a DNR rule |
| getBadFilterRules() | NetworkRule[] | $badfilter rules in this set |
| getRulesHashMap() | IRulesHashMap | Hash map for fast $badfilter matching |
| serializeCompact(unsafeRules, prettyPrint?) | Promise<string> | Compact JSON serialization |
| unloadContent() | void | Release lazy-loaded content |
import type { ConversionResult } from '@adguard/dnr-converter';Result returned by converter methods:
| Property | Type | Description |
| --- | --- | --- |
| ruleset | IRuleset / IRulesetWithSourceMap | The converted rule set |
| errors | (ConversionError \| Error)[] | Rules that could not be converted |
| limitations | LimitationError[] | Warnings about exceeded limits |
| declarativeRulesToCancel | UpdateStaticRulesOptions[]? | Static rule IDs to disable (from computeRulesToDisable) |
MetadataRuleset
import { MetadataRuleset, METADATA_RULESET_ID } from '@adguard/dnr-converter';A specialized ruleset that stores checksums and additional properties for a
collection of DNR rule sets. It serializes as a single-element JSON array
containing a declarative rule with a metadata field, acting as a data
carrier within serialized ruleset files (never matches real requests).
METADATA_RULESET_ID is the constant 0; the ruleset's string ID is always
"ruleset_0".
Checksum methods:
| Method | Returns | Description |
| --- | --- | --- |
| getId() | string | Always "ruleset_0" |
| setChecksum(rulesetId, checksum) | void | Store checksum for a rule set |
| getChecksum(rulesetId) | string \| undefined | Retrieve checksum, or undefined if not set |
| getRulesetIds() | string[] | All rule set IDs that have checksums |
Additional-property methods:
| Method | Returns | Description |
| --- | --- | --- |
| setAdditionalProperty(key, value) | void | Store an arbitrary JSON-serializable property |
| getAdditionalProperty(key) | unknown \| undefined | Retrieve a property value |
| hasAdditionalProperty(key) | boolean | Check whether a property exists |
| removeAdditionalProperty(key) | void | Remove a property (no-op if missing) |
Serialization:
| Method | Returns | Description |
| --- | --- | --- |
| serialize(pretty?) | string | JSON string; pretty=true for human-readable output |
| MetadataRuleset.deserialize(json) | MetadataRuleset | Reconstruct from a serialized string; throws on invalid input |
const meta = new MetadataRuleset();
meta.setChecksum('ruleset_1', 'abc123');
meta.setAdditionalProperty('version', '2.0');
const json = meta.serialize();
const restored = MetadataRuleset.deserialize(json);
console.log(restored.getChecksum('ruleset_1')); // "abc123"
console.log(restored.getAdditionalProperty('version')); // "2.0"isSafeRule(rule)
import { isSafeRule } from '@adguard/dnr-converter';
// DeclarativeRule from the DNR API
const rule = {
id: 2,
priority: 1,
action: { type: 'block' },
condition: { urlFilter: 'example.com' },
};
isSafeRule(rule); // true — "block" is a safe actionReturns true if the declarative rule's action is one of the
safe rule actions (block, allow,
allowAllRequests, upgradeScheme). Useful for separating safe static rules
from unsafe dynamic rules that require additional review.
DNR_CONVERTER_VERSION
import { DNR_CONVERTER_VERSION } from '@adguard/dnr-converter';
console.log(DNR_CONVERTER_VERSION); // e.g. "0.0.1"A string constant with the current library version.
Network rule types
The package exports types related to network rule parsing and validation:
import {
Rule,
HttpHeaderMatcher,
RuleDeclarativeValidator,
} from '@adguard/dnr-converter';Rule— parsed network rule with accessors for domains, resource types, methods, advanced modifier values, priority, and option flags. Create instances viaRule.createFromText(filterListId, index, text).HttpHeaderMatcher— type describing the parsed$headermodifier value.RuleDeclarativeValidator— static helper that checks whether aRulecan be converted to a DNR rule. CallRuleDeclarativeValidator.shouldConvertRule(rule)— returnstrueif the rule is convertible,falseif it should be silently skipped, or throwsUnsupportedModifierErrorif the rule uses an unsupported modifier.
CLI
The package ships a dnr-converter command-line tool for converting filter
lists to DNR rulesets and extracting filter content back from compiled rulesets.
Commands
convert
Converts AdGuard filter lists from a metadata directory into DNR rulesets.
dnr-converter convert \
./filters \
./resources \
./dist/rulesetsArguments:
| Argument | Description | Default |
| --- | --- | --- |
| <filters_and_metadata_dir> | Directory containing filters.json and filter list files | (required) |
| <resources_dir> | Directory with redirect resources | (required) |
| [dest_rule_sets_dir] | Output directory for generated rulesets | ./build/rulesets |
Options:
| Option | Description | Default |
| --- | --- | --- |
| --debug | Enable debug logging | false |
| --prettify-json <bool> | Pretty-print JSON output files | true |
| --additional-properties <json> | Additional properties to include in the metadata ruleset as JSON string | {} |
The filters.json metadata file must be an array of filter metadata objects
each with at minimum filterId and name fields.
extract-filters
Extracts original filter list content that was embedded in compiled DNR rulesets
by the convert command.
dnr-converter extract-filters \
./dist/rulesets \
./extracted-filtersArguments:
| Argument | Description |
| --- | --- |
| <path-to-rulesets> | Directory containing compiled ruleset files |
| <path-to-output> | Directory to write extracted filter files |
Programmatic API
The CLI logic is also available as a programmatic API via the
@adguard/dnr-converter/cli subpath export:
import { convertFilters, generateMD5Hash, type ConvertFiltersOptions } from '@adguard/dnr-converter/cli';
await convertFilters(
'./filters',
'./resources',
'./dist/rulesets',
{ debug: true, prettifyJson: true },
);