Browser-Syntax Engine
Bloqr Compiler compiles two distinct filter-list grammars:
- DNS/hosts-style — domain-oriented rules (
||example.com^,0.0.0.0 example.com) consumed by DNS resolvers (AdGuard Home/DNS, Pi-hole). This is the original, and still default, engine. - Browser-syntax — AdGuard’s cosmetic/network/scriptlet grammar (
##.selector,$script,third-party,#@#, redirect/scriptlet calls) consumed by browser extensions.
One compiler handles both. A configuration’s sources are routed to whichever engine understands their grammar — automatically, or forced via a CLI flag or per-source override — and DNS and browser-syntax rules are always compiled and written to separate output artifacts, never merged, since they’re consumed by different parsers.
How routing works
Each source resolves to an engine ('dns' or 'browser') via this precedence, first
match wins:
- Explicit source override —
source.engine: 'dns' | 'browser'in the configuration. - Legacy explicit —
source.type === 'hosts'always resolves to'dns'(hosts syntax has no browser-syntax equivalent). - Content sniffing — a lightweight, dependency-free classifier
(
classifyLine/detectEngineFromLinesinsrc/engines/EngineDetector.ts) looks at cosmetic separators (##,#@#, …), hosts-file syntax, and browser-only network modifiers ($script,$csp=,$redirect=, …) to vote per line. - Configuration default —
configuration.defaultEngine, falling back to'dns'— so every configuration written before this feature existed keeps behaving exactly as it did.
The CLI’s --engine flag (auto | dns | browser) can force every source through
one engine, bypassing steps 1–4 entirely.
{ "name": "Mixed List", "sources": [ { "source": "https://example.org/hosts.txt", "type": "hosts" }, { "source": "https://example.org/cosmetic.txt", "engine": "browser" } ]}The two engines
| DNS engine | Browser-syntax engine | |
|---|---|---|
| Class | FilterCompiler | BrowserSyntaxCompiler |
| Parsing | @adguard/agtree (via AGTreeParser/RuleUtils) | same — already shared infrastructure |
| Default transformations | RemoveComments, Deduplicate, Compress, Validate, TrimLines, InsertFinalNewLine | RemoveComments, Deduplicate, RemoveEmptyLines, TrimLines, InsertFinalNewLine |
| Rejects | — | Compress, Validate, ValidateAllowIp, InvertAllow (DNS-only; see below) |
Compress and Validate are written specifically against DNS/hosts-style rules —
Compress converts hosts-format entries to ||domain^ and dedupes by hostname;
Validate enforces the DNS-blocker-supported modifier allowlist and domain-only
patterns. Both would silently corrupt or strip valid cosmetic/network browser rules,
so BrowserSyntaxCompiler rejects them outright if requested directly, rather than
running them and producing a broken list.
When a single configuration mixes both engines, MultiEngineCompiler orchestrates
both compilers and returns one CompilationResult per engine present. A shared
top-level transformations list (e.g. one built from CLI defaults) has any DNS-only
entries silently filtered out for the browser bucket, so one config file doesn’t need
a separate transformations list per engine to avoid an error — see
MultiEngineCompiler’s filterToBrowserSafe.
CLI usage
# Auto-detect per source (default) — mixed config writes two filesbloqr-compiler -c config.json -o list.txt# -> list.txt (DNS rules) + list.browser.txt (browser-syntax rules, derived name)
# Name the browser-syntax output explicitlybloqr-compiler -c config.json -o list.txt --browser-output cosmetic.txt
# Force every source through one enginebloqr-compiler -c config.json -o list.txt --engine dnsbloqr-compiler -c config.json -o list.txt --engine browserWhen every source in a configuration resolves to the DNS engine (still true for 100% of pre-existing configs), the CLI’s original single-file code path runs completely unchanged — this feature is purely additive.
--stdout is not supported for a mixed-engine configuration (two output streams
can’t share stdout); use --output and --browser-output instead.
Library usage
import { MultiEngineCompiler } from '@bloqr/compiler';
const compiler = new MultiEngineCompiler();const { dns, browser } = await compiler.compile(configuration);
// dns / browser are each `CompilationResult | undefined` — present only when the// configuration had at least one source resolving to that engine.BrowserSyntaxCompiler can also be used standalone for a browser-syntax-only
configuration, with the same compile() / compileWithMetrics() shape as
FilterCompiler.
What’s not implemented yet
worker/API — noenginefield on compile requests, no per-engine output URLs. The Cloudflare Worker only ever produces DNS-engine output today.frontend/Angular UI — no engine selection or browser-syntax output display.- Cosmetic-AST-aware
Deduplicate— the browser engine’s defaultDeduplicateis exact-string dedup (safe, but a cosmetic rule that’s semantically identical under a different serialization won’t be caught). @adguard/tsurlfilter-based validation — a browser-syntax equivalent of the DNS engine’sValidatetransformation, i.e. rejecting rules that don’t parse into something a real browser engine would accept, rather than only rejecting the DNS-only transformation names.
See also
- RFC: Browser-Syntax Engine Integration — original design proposal and rationale.
- CLI Reference — full
--engine/--browser-outputflag reference.
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.