Jesse A. Green / notebook

uBlock Origin — Administrator & Developer Guide

Generated May 2026 from gorhill/uBlock at afbb85c

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Architecture Overview

uBlock Origin is a wide-spectrum content blocker for modern web browsers, created and maintained by Raymond Hill. Unlike simple ad blockers, uBlock Origin implements a multi-layered filtering architecture that processes network requests, modifies page content cosmetically, injects scriptlets to neutralize tracking scripts, and rewrites HTML before rendering. Every layer operates through purpose-built data structures optimized for minimal memory consumption and sub-millisecond matching latency, handling hundreds of thousands of filter rules simultaneously.

The extension is structured as a platform-abstracted core with browser-specific adapters. The filtering engines, data structures, UI modules, and serialization logic all live in src/js/, while platform/ contains per-browser entry points that adapt the shared core to each browser's extension API surface. This separation allows a single codebase to target Chrome (MV2), Firefox, Opera, Thunderbird, Safari, and the Manifest V3 variant (uBOL), as well as a headless Node.js package (@gorhill/ubo-core) for server-side filter matching.

The architecture prioritizes correctness and performance over developer convenience. Filter compilation transforms human-readable filter syntax into compact internal representations stored in custom trie structures. WASM modules accelerate hot paths in Firefox. A custom LZ4-based serialization system compresses engine state for fast persistence and restoration, avoiding the overhead of JSON serialization for the large filter datasets involved.

Architecture Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                        Browser Extension Host                       │
│                                                                     │
│  ┌──────────────────────── Background Page ───────────────────────┐ │
│  │                                                                │ │
│  │  ┌─────────────┐  ┌──────────────┐  ┌──────────────────────┐  │ │
│  │  │  Asset       │  │  Filter List │  │  Settings Manager    │  │ │
│  │  │  Manager     │  │  Compiler    │  │  (user + advanced +  │  │ │
│  │  │  (fetch,     │  │  (text →     │  │   managed storage)   │  │ │
│  │  │   cache,     │  │   internal   │  └──────────────────────┘  │ │
│  │  │   update)    │  │   repr)      │                            │ │
│  │  └──────┬───────┘  └──────┬───────┘                            │ │
│  │         │                 │                                    │ │
│  │         ▼                 ▼                                    │ │
│  │  ┌────────────────────────────────────────────────────────┐    │ │
│  │  │              Filtering Engine Stack                     │    │ │
│  │  │                                                        │    │ │
│  │  │  ┌────────────────────┐  ┌──────────────────────────┐  │    │ │
│  │  │  │ Static Net Filter  │  │ Cosmetic Filtering       │  │    │ │
│  │  │  │ Engine             │  │ Engine                    │  │    │ │
│  │  │  │ (biditrie-based)   │  │ (CSS selectors, proc.)   │  │    │ │
│  │  │  └────────────────────┘  └──────────────────────────┘  │    │ │
│  │  │  ┌────────────────────┐  ┌──────────────────────────┐  │    │ │
│  │  │  │ Dynamic Net Filter │  │ Scriptlet Filtering      │  │    │ │
│  │  │  │ (per-host rules)   │  │ Engine                   │  │    │ │
│  │  │  └────────────────────┘  └──────────────────────────┘  │    │ │
│  │  │  ┌────────────────────┐  ┌──────────────────────────┐  │    │ │
│  │  │  │ Dynamic URL Rule   │  │ HTML Filtering           │  │    │ │
│  │  │  │ Filtering          │  │ (Firefox only)           │  │    │ │
│  │  │  └────────────────────┘  └──────────────────────────┘  │    │ │
│  │  │  ┌────────────────────┐  ┌──────────────────────────┐  │    │ │
│  │  │  │ Redirect Engine    │  │ HN Switches              │  │    │ │
│  │  │  │ (surrogates)       │  │ (per-host toggles)       │  │    │ │
│  │  │  └────────────────────┘  └──────────────────────────┘  │    │ │
│  │  └────────────────────────────────────────────────────────┘    │ │
│  │         │                                                      │ │
│  │         ▼                                                      │ │
│  │  ┌──────────────┐  ┌─────────────┐  ┌──────────────────────┐  │ │
│  │  │ Serializer   │  │ WASM Accel  │  │ Data Structures      │  │ │
│  │  │ (s14e + LZ4) │  │ (biditrie,  │  │ (BidiTrie, HNTrie,   │  │ │
│  │  │              │  │  hntrie,    │  │  MRUCache, JSONPath)  │  │ │
│  │  │              │  │  PSL, LZ4)  │  │                      │  │ │
│  │  └──────────────┘  └─────────────┘  └──────────────────────┘  │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                              │                                      │
│                    vAPI messaging                                    │
│                              │                                      │
│  ┌───────────────── Content Scripts ─────────────────────────────┐  │
│  │  contentscript.js         contentscript-extra.js              │  │
│  │  (DOM observation,        (ProceduralFilterer,                │  │
│  │   declarative cosmetic)    18+ PSelectorTask types)           │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌──────────────────── UI Pages ────────────────────────────────┐  │
│  │  Popup Panel │ Dashboard (filters, lists, settings, logger)  │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  Platform Adapters                                                  │
│  platform/chromium/  │  platform/firefox/  │  platform/opera/       │
│  platform/mv3/       │  platform/nodejs/   │  platform/thunderbird/ │
│  platform/safari/    │  platform/common/                            │
└─────────────────────────────────────────────────────────────────────┘

Note

The architecture is deliberately modular — each filtering engine is independent and can be individually disabled, serialized, or tested in isolation. The platform/nodejs/ adapter exposes only the Static Net Filtering Engine, which is the largest and most complex component.

Component Inventory

Component Source Location Responsibility
Static Net Filtering Engine src/js/static-net-filtering.js URL-pattern matching with 60+ filter classes, trie-based
Cosmetic Filtering Engine src/js/cosmetic-filtering.js CSS element hiding and injection
Scriptlet Filtering Engine src/js/scriptlet-filtering-core.js, scriptlet-filtering.js JavaScript injection to neutralize trackers
Dynamic Net Filtering src/js/dynamic-net-filtering.js Per-hostname allow/block matrix
Dynamic URL Rule Filtering src/js/url-net-filtering.js Per-URL dynamic rules
HTML Filtering src/js/html-filtering.js Pre-render HTML content modification (Firefox only)
Redirect Engine src/js/redirect-engine.js Surrogate resource serving
HN Switches src/js/hnswitches.js Per-hostname feature toggles
BidiTrieContainer src/js/biditrie.js Bidirectional trie for fast string matching
HNTrieContainer src/js/hntrie.js Hostname-specialized trie
MRUCache src/js/mrucache.js Most-recently-used eviction cache
JSONPath src/js/jsonpath.js JSON path expression evaluator
Serializer src/js/s14e-serializer.js LZ4-compressed custom serialization
ProceduralFilterer src/js/contentscript-extra.js Procedural cosmetic filtering orchestration
Platform Abstraction (vAPI) platform/common/vapi-*.js Cross-browser API normalization
Node.js API platform/nodejs/index.js Headless SNFE for server-side use

Technology Stack

Layer Technology
Language JavaScript (ES modules)
Extension API WebExtensions (MV2), declarativeNetRequest (MV3)
WASM modules biditrie, hntrie, publicsuffixlist, LZ4 (with JS fallback)
UI framework Vanilla JS, CodeMirror 5.x (filter editor)
Compression LZ4 (custom LZ4BlockJS class in s14e-serializer.js)
Bundled libraries CodeMirror 5.x, csstree, diff, hsluv, js-beautify, lz4, publicsuffixlist, regexanalyzer
Linting ESLint + globals (dev dependency)
License GPLv3

Data Flow

A network request passes through filtering in this order:

  1. HN Switches — checked first; if a per-hostname switch (e.g., "no strict blocking") is set, it can modify subsequent engine behavior.
  2. Dynamic Net Filtering — per-hostname allow/block rules from the matrix UI. If a dynamic rule matches with an explicit allow or block, processing may short-circuit.
  3. Dynamic URL Rule Filtering — per-URL rules set via the logger UI.
  4. Static Net Filtering Engine — the main rule-matching engine. Compiled filter lists are evaluated against the request URL, origin, and type. This is where the bulk of the 300,000+ EasyList-derived rules execute.
  5. Redirect Engine — if the request is blocked but a redirect surrogate is available, the request is redirected to a neutered resource instead of being cancelled.
  6. Cosmetic Filtering Engine — injects CSS to hide elements matching cosmetic filter rules. Runs in content scripts.
  7. Scriptlet Filtering Engine — injects JavaScript snippets to defuse anti-adblock scripts and tracking mechanisms.
  8. HTML Filtering — on Firefox, modifies HTML response bodies before they reach the DOM renderer (using filterResponseData()).

2. System Requirements

Browser Requirements

Browser Minimum Version Extension Manifest Notes
Google Chrome 93.0 MV2 Primary development target
Firefox Current ESR+ MV2 WASM enabled by default; HTML filtering supported
Opera Current MV2 Uses Chromium adapter
Thunderbird Current MV2 Email content filtering
Safari Current — Platform adapter in platform/safari/
Chrome (MV3) — MV3 uBOL variant, uses declarativeNetRequest

Browser Permissions

The Chromium MV2 manifest declares the following permissions:

Permission Purpose
contextMenus Right-click menu integration for element blocking
privacy Access to browser privacy settings (e.g., WebRTC leak prevention)
storage Extension storage for settings and filter data
tabs Tab URL access for per-site filtering decisions
unlimitedStorage Large filter list datasets exceed default quotas
webNavigation Page lifecycle tracking for cosmetic filter injection timing
webRequest Network request interception (core blocking mechanism)
webRequestBlocking Synchronous request blocking/modification
<all_urls> Content script injection and request observation on all origins

Important

The <all_urls> and webRequestBlocking permissions are fundamental to uBlock Origin's operation. Enterprise administrators deploying via group policy should understand that these permissions grant the extension visibility into all network traffic within the browser. This is by design — a content blocker cannot function without observing what to block.

Node.js Requirements (npm package)

Requirement Version
Node.js (repository root tooling) >= 22
Node.js (@gorhill/ubo-core npm package) >= 18
npm package version @gorhill/ubo-core v0.1.30
Runtime dependencies Zero (self-contained)

3. Configuration Reference

uBlock Origin has three configuration layers: user settings (exposed in the dashboard), advanced settings (hidden, power-user), and managed storage (enterprise deployment). Managed storage takes precedence over user settings when deployed via browser policy.

Managed Storage Schema (Enterprise Deployment)

The managed storage schema is defined in platform/common/managed_storage.json and provides the mechanism for enterprise administrators to control uBlock Origin configuration across an organization. All keys are optional.

Key Type Description
adminSettings String (JSON) A JSON string in uBlock Origin's backup format that overwrites all local settings. This is the nuclear option — it replaces the user's entire configuration.
advancedSettings Array of [name, value] pairs Overrides specific advanced (hidden) settings. Each entry is a two-element array: ["settingName", "value"].
userSettings Array of [name, value] pairs Overrides specific user-facing settings. Same format as advancedSettings.
disableDashboard Boolean When true, prevents users from accessing the settings dashboard entirely.
disabledPopupPanelParts Array of strings Hides specific sections of the popup panel UI.
toAdd Object Additive configuration — merges with the user's existing settings rather than replacing them.
toAdd.trustedSiteDirectives Array of strings Domain names to add to the user's trusted sites (whitelist). Example: ["intranet.company.com", "wiki.company.com"]
toOverwrite Object Replacement configuration — completely overwrites the specified user settings.
toOverwrite.filters Array of strings Custom filter lines that replace the user's custom filters entirely. Each string is one filter rule.
toOverwrite.filterLists Array of strings Filter list identifiers or URLs that replace the user's subscribed filter lists.
toOverwrite.trustedSiteDirectives Array of strings Trusted site domains that replace the user's entire whitelist.

Important

The adminSettings key accepts a JSON string (not an object) — it must be the serialized form of a uBlock Origin backup. Using adminSettings overwrites everything, while toAdd and toOverwrite allow surgical modifications. Prefer toAdd and toOverwrite for maintainability.

Managed Storage Example (Chrome Enterprise)

{
  "name": "uBlock Origin",
  "description": "Enterprise-managed uBlock Origin configuration",
  "type": "object",
  "properties": {
    "advancedSettings": {
      "value": [
        ["userResourcesLocation", "https://internal.company.com/ubo-resources.txt"]
      ]
    },
    "disableDashboard": {
      "value": false
    },
    "toAdd": {
      "value": {
        "trustedSiteDirectives": [
          "intranet.company.com",
          "*.internal.company.com",
          "crm.company.com"
        ]
      }
    },
    "toOverwrite": {
      "value": {
        "filterLists": [
          "user-filters",
          "ublock-filters",
          "ublock-badware",
          "ublock-privacy",
          "ublock-unbreak",
          "easylist",
          "easyprivacy",
          "urlhaus-1",
          "plowe-0",
          "https://internal.company.com/custom-blocklist.txt"
        ]
      }
    }
  }
}

Advanced (Hidden) Settings

Advanced settings are accessible through the dashboard by enabling "I am an advanced user" and navigating to the advanced settings page. They are presented as a key-value editor in advanced-settings.js. These settings control internal engine behavior and are not intended for casual users.

Enterprise administrators can set advanced settings via managed storage using the advancedSettings key as an array of [name, value] pairs. The userResourcesLocation setting is particularly relevant for enterprise deployments, allowing administrators to specify internal URLs for custom scriptlet resources.

User Settings

User settings are the standard preferences exposed in the dashboard UI. Enterprise administrators can override them via managed storage using the userSettings key. See the User Guide for a description of each user-facing setting.

Filter List Management Configuration

Filter lists are organized into categories as defined in 3p-filters.js:

Category ID Description
user User-defined custom filters
default Default built-in lists (uBlock filters, EasyList, etc.)
ads Advertising-focused lists
privacy Privacy/tracking protection lists
malware Malware domain lists
multipurpose Multi-purpose lists
cookies Cookie notice lists
social Social media widget lists
annoyances Annoyance lists (popups, newsletters, etc.)
regions Regional/language-specific lists
unknown Uncategorized lists
custom User-imported external lists (by URL)

Filter lists can be specified by their built-in identifier (e.g., easylist, easyprivacy, ublock-filters) or by full URL for custom lists. When deploying via managed storage, use the toOverwrite.filterLists array to define the exact set of lists.


4. Deployment & Scaling

Enterprise Deployment via Managed Storage

The primary enterprise deployment mechanism uses the browser's managed storage API, which allows organization administrators to push configuration through group policy (Windows), configuration profiles (macOS), or JSON policy files (Linux).

Chrome (Windows Group Policy):

  1. Install the Chrome ADMX templates.
  2. Navigate to Computer Configuration > Administrative Templates > Google Chrome > Extensions.
  3. Use "Configure extension management settings" to force-install uBlock Origin and provide managed storage JSON.
  4. The managed storage schema from platform/common/managed_storage.json defines all accepted keys.

Chrome (Linux managed storage):

Place a JSON file at /etc/opt/chrome/managed_storage/cjpalhdlnbpafiamejdnhcphjbkeiagm.json (where the long string is the Chrome Web Store extension ID).

Firefox (Enterprise Policy):

Firefox uses the ExtensionSettings policy or managed-storage-manifests directory. The extension ID for Firefox is [email removed].

Note

The disableDashboard managed storage key is particularly useful in kiosk or locked-down environments where end users should not modify content blocking behavior.

Group Policy / MDM Deployment Patterns

Common enterprise deployment patterns:

Pattern Approach Keys Used
Lock down completely Force-install extension, disable dashboard, overwrite all settings adminSettings + disableDashboard: true
Allow user customization with baseline Set baseline filter lists and trusted sites, leave dashboard open toOverwrite.filterLists + toAdd.trustedSiteDirectives
Add internal sites to whitelist Merge trusted sites without affecting user's custom config toAdd.trustedSiteDirectives
Custom filter lists only Replace all filter subscriptions with organization-approved lists toOverwrite.filterLists
Hide popup panel sections Simplify the UI for non-technical users disabledPopupPanelParts

MV3 Migration Considerations

uBlock Origin's MV3 variant, uBlock Origin Lite (uBOL), lives in platform/mv3/ and uses Chrome's declarativeNetRequest API instead of webRequest/webRequestBlocking.

Key architectural differences in MV3:

  • No synchronous blocking — declarativeNetRequest is declarative, not programmatic. The browser evaluates rules, not the extension.
  • Rule count limits — Chrome imposes static and dynamic rule count caps that constrain the number of filter rules uBOL can load simultaneously.
  • No HTML filtering — filterResponseData() is not available in MV3.
  • Reduced cosmetic filtering — procedural cosmetic filters have limited support.
  • No redirect surrogates — the Redirect Engine operates differently under MV3 constraints.

Administrators planning for the Chrome MV3 transition should understand that uBOL is a functionally reduced version. Organizations requiring full filtering capabilities should evaluate Firefox or advocate for MV2 extension support in their browser policy.

Note

As of this writing, Chrome continues to support MV2 extensions for enterprise-managed installations. Administrators can use the ExtensionManifestV2Availability Chrome policy to extend MV2 support.


5. Operations

Filter List Update Lifecycle

Filter list updates follow this cycle:

  1. Check interval — uBlock Origin periodically checks for updates to subscribed filter lists. The default interval is configurable through advanced settings.
  2. Fetch — Updated list content is downloaded from the list's URL.
  3. Compile — The raw filter text is compiled by FilterCompiler into the internal representation used by each engine.
  4. Load — Compiled rules are loaded into the appropriate filtering engines (static net, cosmetic, scriptlet, etc.).
  5. Serialize — Engine state is serialized via s14e-serializer.js using LZ4 compression and persisted to extension storage.
  6. Cache — The raw list content and compiled state are cached. On subsequent browser starts, the extension deserializes cached state rather than recompiling from raw text.

The 3p-filters.js UI module provides the interface for managing filter list subscriptions, triggering manual updates, and searching across all available lists.

Tip

When deploying custom internal filter lists via managed storage, host them on an internal HTTPS endpoint with proper cache headers. The asset manager respects standard HTTP caching semantics during the fetch phase.

Asset Caching and Expiration

Assets (filter lists, redirect resources) are managed by the asset manager, which stores content in extension storage. Key behaviors:

  • Compiled cache — Compiled filter state is persisted separately from raw list content. If the compiled cache is valid, the extension skips recompilation on startup.
  • Selfie mechanism — The term "selfie" in the codebase refers to a serialized snapshot of the entire engine state. When the engine detects its selfie is invalid (e.g., after a list update), it logs "Bad selfie" and triggers full recompilation from raw filter text.
  • Storage quotas — The unlimitedStorage permission ensures that large filter datasets (which can exceed 100 MB when compiled) do not hit browser storage limits.

Backup and Restore Procedures

uBlock Origin's backup system produces a JSON-formatted backup file containing all user settings, custom filters, trusted site directives, and filter list subscriptions. The backup format is the same format accepted by the adminSettings managed storage key.

Backup: Dashboard > Settings > "Backup to file" exports a JSON file.

Restore: Dashboard > Settings > "Restore from file" imports a previously exported backup.

For enterprise deployments, a backup file can be captured from a properly configured reference installation and distributed as the adminSettings value in managed storage. See the User Guide for step-by-step backup instructions.

Tip

Periodically export a backup from a reference installation and version-control it. This serves as both documentation of the approved configuration and a quick recovery mechanism.

Cloud Sync Architecture

The custom filters editor (1p-filters.js) includes cloud sync capability for synchronizing custom filter rules across devices. This uses the browser's built-in extension sync storage (browser.storage.sync). Cloud sync is limited by the browser's sync storage quotas (typically 100 KB total, 8 KB per item in Chrome).


6. Performance

Memory Architecture

uBlock Origin's memory efficiency is achieved through custom data structures that avoid the overhead of JavaScript objects for large datasets:

BidiTrieContainer (biditrie.js) — A bidirectional trie that stores filter patterns in a compact array-based format. Unlike a traditional trie that matches left-to-right, the bidirectional trie can match patterns from either direction, enabling efficient lookup for both prefix and suffix patterns. The trie uses typed arrays (Uint32Array) for the node table, avoiding the per-object overhead of JavaScript objects.

HNTrieContainer (hntrie.js) — A specialized trie for hostname matching. Hostnames are stored reversed (e.g., com.example instead of example.com) so that domain hierarchies naturally form trie branches. This enables efficient matching of hostname-based filter rules including wildcard domain patterns.

MRUCache (mrucache.js) — A most-recently-used cache used throughout the filtering pipeline to avoid redundant lookups. Recently evaluated URLs and their results are cached, so repeated requests to the same resource (common during page loads) resolve instantly.

Typed array storage — Both trie implementations store their data in typed arrays rather than linked node objects. This provides cache-friendly memory layout, eliminates GC pressure from millions of small objects, and enables direct serialization without traversal.

WASM Acceleration

WebAssembly modules accelerate the performance-critical trie operations:

WASM Module Source Purpose
src/js/wasm/biditrie.wasm Compiled from biditrie.wat Accelerates bidirectional trie lookups
src/js/wasm/hntrie.wasm Compiled from hntrie.wat Accelerates hostname trie lookups
src/lib/publicsuffixlist/wasm/publicsuffixlist.wasm — Accelerates public suffix list lookups
src/lib/lz4/ — LZ4 compression/decompression with JS fallback

WASM is enabled by default in Firefox. In Chrome, WASM usage requires the 'wasm-unsafe-eval' Content Security Policy directive, which may not be available in all extension contexts.

Note

When WASM modules are unavailable (due to CSP restrictions or platform limitations), the engines automatically fall back to pure JavaScript implementations. Performance is reduced but functionality is identical.

Filter Matching Performance Characteristics

The Static Net Filtering Engine processes requests through a multi-stage pipeline designed for early rejection:

  1. Token extraction — The request URL is tokenized. Tokens are substrings that serve as candidate lookup keys.
  2. Token hash lookup — Each token is hashed and looked up in a bucket map. Most tokens produce no match, allowing rapid rejection of the majority of filter rules.
  3. Bucket evaluation — When a token matches a bucket, the filters within that bucket are evaluated. Buckets may contain individual filters or nested FilterBucket > FilterBucketIf structures that apply additional preconditions (origin-based, regex-based) before evaluating their contents.
  4. Pattern matching — Individual filter patterns are matched against the URL using the bidirectional trie.
  5. Domain/entity matching — Domain and entity restrictions are evaluated using the hostname trie.

This architecture means that the engine's performance scales with the number of distinct tokens, not the total number of filter rules. Adding more filter rules to existing token buckets has minimal marginal cost.

Cosmetic Filtering Overhead

Cosmetic filtering operates in content scripts and has a different performance profile than network filtering:

  • Declarative cosmetic filters — Simple CSS selectors (e.g., ##.ad-banner) are batched and injected as a single <style> element. This is handled by the browser's native CSS engine and has negligible overhead.
  • Procedural cosmetic filters — Complex selectors using :has-text(), :if(), :xpath(), and other procedural operators require DOM traversal by the ProceduralFilterer. These are more expensive and run via MutationObserver to react to dynamic content changes.

Note

The procedural cosmetic filtering system (contentscript-extra.js) implements 18+ PSelectorTask subtypes. Each task type has different performance characteristics. :xpath() tasks are typically the most expensive as they invoke the browser's XPath engine. Administrators managing custom filter lists should prefer simple CSS selectors where possible.

Optimization Strategies

For enterprise deployments managing performance:

  • Reduce filter list count — Each additional filter list adds compilation time and memory. Only subscribe to lists relevant to your region and use case.
  • Prefer static over dynamic rules — Dynamic net filtering rules (the per-hostname matrix) are evaluated sequentially. Static rules benefit from the trie-based acceleration.
  • WASM in Firefox — For performance-critical deployments, Firefox's default WASM support provides measurable improvement in filter matching speed.
  • Monitor via the logger — The network request logger (logger-ui.js) shows which rules match each request, enabling identification of expensive or redundant rules.

7. Security

Permission Model

uBlock Origin requires broad permissions by necessity — a content blocker must observe and intercept network traffic to function. The security model is designed around minimizing the attack surface despite these broad permissions:

  • No remote code execution — All filtering logic is bundled with the extension. Filter lists contain declarative rules, not executable code. Scriptlet injections are pre-defined within the extension, not fetched from filter lists.
  • No external service dependencies — The extension does not phone home, does not have accounts, and does not transmit browsing data. Filter list fetches are the only network activity.
  • Content script isolation — Content scripts run in an isolated world with no access to the page's JavaScript context. Communication with the background page uses the extension messaging API exclusively.

Warning

The <all_urls> permission grants the extension access to observe all browser network traffic, including HTTPS requests. While uBlock Origin processes this data locally and does not transmit it, a compromised extension installation could theoretically exfiltrate browsing data. Always install from official distribution channels (Chrome Web Store, Firefox Add-ons) and verify that automatic updates are enabled.

Content Script Isolation

Content scripts (contentscript.js, contentscript-extra.js) operate under the browser's content script isolation model:

  • Separate JavaScript context — Content scripts cannot access page JavaScript variables or functions, and vice versa.
  • DOM access only — Content scripts can read and modify the DOM but cannot intercept the page's network requests directly.
  • Messaging channel — All communication between content scripts and the background page uses browser.runtime.sendMessage() / browser.runtime.onMessage, which is type-checked and validated.

Privileged Origin Validation

The vAPI background implementation (vapi-background.js) handles tab management, storage, and browser settings. Access to privileged browser APIs (privacy settings, context menus) is restricted to the background page context, which runs with the extension's full permission set.

Anti-Tampering Measures

  • Selfie validation — Serialized engine state includes integrity markers. The "Bad selfie" error (logged when validation fails) triggers a full recompilation from source filter lists, preventing tampering with cached filter state.
  • Filter compiler isolation — The FilterCompiler processes raw filter text into an internal binary representation. Malformed filter rules are rejected during compilation rather than causing runtime errors.
  • Redirect resource validation — The RedirectEngine only serves redirect resources that are bundled with the extension. Filter lists can reference redirect targets by name, but cannot inject arbitrary content.

Managed Storage Security

Caution

The adminSettings managed storage key accepts a complete configuration backup as a JSON string. A malicious or poorly constructed adminSettings value could disable all filtering, whitelist malicious domains, or remove all filter lists. Treat managed storage policy files with the same security controls as any other system configuration pushed via group policy.

When using managed storage for enterprise deployment:

  • Restrict write access to managed storage policy files to authorized administrators only.
  • Version-control policy files and review changes.
  • Use toAdd and toOverwrite for incremental changes rather than adminSettings for full replacement, reducing the risk of accidentally clearing user protections.
  • Test policy changes against a reference installation before fleet-wide deployment.

8. Developer Internals

Repository Structure

gorhill/uBlock/
├── src/
│   ├── js/                    # Core JavaScript (79 files)
│   │   ├── static-net-filtering.js    # Static Net Filtering Engine (largest)
│   │   ├── cosmetic-filtering.js      # Cosmetic Filtering Engine
│   │   ├── scriptlet-filtering-core.js # Scriptlet core
│   │   ├── scriptlet-filtering.js     # Scriptlet engine extension
│   │   ├── dynamic-net-filtering.js   # Dynamic per-host rules
│   │   ├── url-net-filtering.js       # Dynamic URL rules
│   │   ├── html-filtering.js          # HTML filtering (Firefox)
│   │   ├── redirect-engine.js         # Redirect/surrogate engine
│   │   ├── hnswitches.js             # Per-host toggle switches
│   │   ├── biditrie.js               # BidiTrieContainer
│   │   ├── hntrie.js                 # HNTrieContainer
│   │   ├── mrucache.js               # MRU cache
│   │   ├── jsonpath.js               # JSON path evaluator
│   │   ├── s14e-serializer.js        # LZ4 serialization
│   │   ├── contentscript-extra.js    # Procedural cosmetic filtering
│   │   ├── popup-fen498.js           # Popup panel UI
│   │   ├── 1p-filters.js            # Custom filters editor
│   │   ├── 3p-filters.js            # Filter list management
│   │   ├── advanced-settings.js      # Hidden settings editor
│   │   ├── logger-ui.js             # Network request logger
│   │   ├── asset-viewer.js          # Filter list viewer
│   │   ├── about.js                 # Version info
│   │   └── wasm/                    # WASM modules + .wat sources
│   ├── css/                   # Stylesheets
│   ├── lib/                   # Bundled third-party libraries
│   │   ├── codemirror/        # CodeMirror 5.x (filter editor)
│   │   ├── csstree/           # CSS parser
│   │   ├── diff/              # Text diff
│   │   ├── hsluv/             # Color space
│   │   ├── js-beautify/       # Code formatting
│   │   ├── lz4/              # LZ4 codec (WASM + JS fallback)
│   │   ├── publicsuffixlist/  # PSL library (WASM accelerated)
│   │   └── regexanalyzer/     # Regex analysis
│   ├── img/                   # Icons and images
│   └── *.html                 # UI pages (popup, dashboard)
├── platform/
│   ├── common/                # Shared platform code
│   │   ├── vapi-background.js # Background page API abstraction
│   │   ├── vapi-client.js     # Content script messaging
│   │   ├── vapi-common.js     # Shared utilities
│   │   └── managed_storage.json # Enterprise config schema
│   ├── chromium/              # Chrome MV2 adapter
│   │   ├── manifest.json      # Extension manifest
│   │   ├── webext.js          # Promisified browser API
│   │   └── vapi-background-ext.js
│   ├── firefox/               # Firefox adapter
│   ├── opera/                 # Opera adapter
│   ├── thunderbird/           # Thunderbird adapter
│   ├── safari/                # Safari adapter
│   ├── mv3/                   # Manifest V3 (uBOL)
│   └── nodejs/                # Node.js headless API
│       └── index.js           # @gorhill/ubo-core entry point
└── .eslintrc.yml              # ESLint configuration

Module Reference (HIGH-Priority Modules)

static-net-filtering.js — Static Net Filtering Engine

The largest and most complex module. Contains the FilterCompiler class for converting text rules into internal representation, and 60+ Filter* classes organized in a deep hierarchy for matching network requests against compiled rules. The engine uses BidiTrieContainer for pattern storage and lookup.

cosmetic-filtering.js — Cosmetic Filtering Engine

Manages CSS-based element hiding. Processes cosmetic filter rules (those starting with ## or #@#) and generates per-page stylesheets injected via content scripts. Maintains a "selfie" (serialized snapshot) for fast startup; emits "Bad selfie" on corruption.

scriptlet-filtering-core.js / scriptlet-filtering.js

Two-layer design: ScriptletFilteringEngine (core) provides the base scriptlet matching and injection logic; ScriptletFilteringEngineEx (extension) adds browser-specific integration. Scriptlets are pre-defined JavaScript snippets that neutralize tracking scripts and anti-adblock mechanisms.

dynamic-net-filtering.js — DynamicHostRuleFiltering

Implements the per-hostname allow/block matrix accessible from the popup panel's advanced mode. Rules are stored as a flat structure keyed by {source-hostname, destination-hostname, request-type} tuples.

biditrie.js — BidiTrieContainer

The core data structure for the Static Net Filtering Engine. Implements a bidirectional trie using typed arrays (Uint32Array). Supports both forward and reverse pattern matching, enabling efficient evaluation of both prefix-anchored and suffix-anchored filter patterns. Has a corresponding WASM implementation for acceleration.

hntrie.js — HNTrieContainer

Hostname-specialized trie that stores reversed hostnames for hierarchical matching. Used by domain-based filter classes (FilterFromDomainHit, FilterToDomainHitSet, etc.) to efficiently evaluate domain restrictions on filter rules.

s14e-serializer.js — Serialization

Custom serialization framework with built-in LZ4 compression via the LZ4BlockJS class. Used to persist and restore engine state across browser sessions. The "s14e" name is an abbreviation of "serializer" (s + 14 letters + e).

contentscript-extra.js — Procedural Cosmetic Filtering

Implements the ProceduralFilterer and 18+ PSelectorTask subtypes for procedural cosmetic filtering. Procedural filters extend CSS selector syntax with operations like :has-text(), :if(), :matches-css(), :xpath(), and :upward().

platform/common/vapi-background.js — vAPI Background

The platform abstraction layer for the background page. Provides a unified API surface (vAPI) for storage, tabs, messaging, browser settings, and extension lifecycle across all supported browsers. Each browser platform extends or overrides specific vAPI methods.

Filter Class Hierarchy Deep Dive

The Static Net Filtering Engine's power comes from its extensive filter class hierarchy. Each class represents a specific type of filter rule optimization:

Filter (base)
├── FilterPatternPlain
│   ├── FilterPatternPlain1          # Single-character patterns
│   └── FilterPatternPlainX          # Multi-character patterns
├── FilterDomainHit                   # Domain restriction base
│   ├── FilterFromDomainHit          # Match if request FROM domain
│   ├── FilterFromDomainMiss         # Match if request NOT from domain
│   ├── FilterFromEntityHit          # Match entity (domain+TLD)
│   ├── FilterFromEntityMiss         # Negate entity match
│   ├── FilterToDomainHit            # Match if request TO domain
│   └── FilterToDomainMiss           # Match if request NOT to domain
├── FilterDomainHitSet                # Set-based domain matching
│   ├── FilterFromDomainHitSet       # FROM any domain in set
│   ├── FilterToDomainHitSet         # TO any domain in set
│   ├── FilterFromDomainMissSet      # NOT from any domain in set
│   └── FilterToDomainMissSet        # NOT to any domain in set
├── FilterCollection                  # Container for multiple filters
│   ├── FilterDomainHitAny           # Match any domain in collection
│   ├── FilterCompositeAll           # All sub-filters must match (AND)
│   └── FilterBucket                 # Token-indexed bucket of filters
│       └── FilterBucketIf           # Conditional bucket
│           ├── FilterBucketIfOriginHits  # Evaluate only if origin matches
│           └── FilterBucketIfRegexHits   # Evaluate only if regex matches
└── FilterCompiler                    # Compiles text → internal repr

FilterBucket optimization — When a token bucket accumulates enough filters, the engine promotes it from a flat FilterBucket to a FilterBucketIf with a precondition check. This avoids evaluating the entire bucket when the precondition (origin hostname or regex) does not match, significantly reducing per-request evaluation cost for heavily populated buckets.

FilterCompositeAll — Represents a filter with multiple constraints that must all match (e.g., a pattern with both domain and type restrictions). The sub-filters are evaluated in order of expected rejection rate, with the cheapest check first.

Static Net Filtering Compilation Pipeline

The journey from a text filter rule to a matched network request:

Raw filter text (e.g., "||example.com^$third-party")
    │
    ▼
FilterCompiler.compile()
    │  Parse syntax: anchors, options, pattern extraction
    │  Validate: reject malformed rules
    │  Classify: determine filter type, domain restrictions, options
    │
    ▼
Internal representation (tokenized, typed)
    │
    ▼
Engine.fromCompiled()
    │  Instantiate appropriate Filter* class
    │  Extract tokens for bucket assignment
    │  Insert into token→bucket map
    │
    ▼
BidiTrieContainer
    │  Store pattern in bidirectional trie
    │  Share trie nodes across filters with common prefixes/suffixes
    │
    ▼
Ready for matching
    │
    ▼
Engine.matchRequest({url, originURL, type})
    │  Tokenize request URL
    │  Look up each token in bucket map
    │  Evaluate matching buckets (BucketIf preconditions first)
    │  Return: 0 (no match), 1 (block), 2 (allow/exception)

Procedural Cosmetic Filtering

The ProceduralFilterer in contentscript-extra.js orchestrates complex element hiding that cannot be expressed with pure CSS selectors. Each procedural filter is a chain of PSelectorTask instances:

Task Class Selector Syntax Function
PSelectorHasTextTask :has-text(regex) Match elements containing text
PSelectorIfTask :if(selector) Conditional selector (alias for :has())
PSelectorIfNotTask :if-not(selector) Negative conditional
PSelectorMatchesCSSTask :matches-css(prop: value) Match computed CSS properties
PSelectorMatchesCSSBeforeTask :matches-css-before(...) Match ::before pseudo-element CSS
PSelectorMatchesCSSAfterTask :matches-css-after(...) Match ::after pseudo-element CSS
PSelectorMinTextLengthTask :min-text-length(n) Match elements with minimum text length
PSelectorSpathTask :spath(selector) Apply subsequent CSS selector
PSelectorUpwardTask :upward(n or selector) Traverse up n ancestors or to matching ancestor
PSelectorXpathTask :xpath(expr) Apply XPath expression
PSelectorWatchAttrs :watch-attr(attr1, attr2) Re-evaluate when attributes change
PSelectorOthersTask :others(...) Match other elements relative to current
PSelectorStyleTask :style(declarations) Apply inline styles instead of hiding

The ProceduralFilterer uses MutationObserver to watch for DOM changes and re-evaluate procedural filters when new elements are added or attributes change.

Code Patterns and Conventions

ES Modules — The codebase uses ES module syntax (import/export) throughout src/js/. Modules are loaded as type="module" in HTML pages or via dynamic import().

vAPI pattern — All browser-specific functionality is accessed through the vAPI global object, which is populated by the platform-specific vapi-*.js files. Code in src/js/ never directly calls browser.* or chrome.* APIs.

Singleton engines — The filtering engines are singletons. The StaticNetFilteringEngine constructor enforces single-instance via an explicit check: "Only a single instance is supported."

Typed array preference — Performance-critical data structures use Uint32Array, Uint8Array, and ArrayBuffer rather than JavaScript objects or Map instances.

Self-descriptive IDs — Filter classes use numeric type IDs for serialization. The mapping between class and ID is maintained within static-net-filtering.js.

Testing

The @gorhill/ubo-core npm package (at platform/nodejs/) includes a test suite for the Static Net Filtering Engine. Tests validate filter compilation, matching correctness, serialization round-trips, and the removeparam (query filtering) functionality.

Run tests with:

cd platform/nodejs
npm test

The root package.json requires Node.js >= 22 for development tooling, while the npm package itself requires Node.js >= 18.

Build Process

uBlock Origin does not use a traditional bundler (no webpack, rollup, or vite). The extension is structured to run directly from source:

  • Development — Load the platform/chromium/ (or platform/firefox/, etc.) directory as an unpacked extension in the browser.
  • Release packaging — Platform-specific packaging scripts create .zip or .xpi files for distribution. The platform/mv3/ directory contains additional build tooling for the Manifest V3 variant.
  • WASM compilation — .wat (WebAssembly Text) files in src/js/wasm/ are compiled to .wasm using wat2wasm from the WebAssembly Binary Toolkit.

Contributing

Contributions follow standard GitHub fork-and-PR workflow. The repository uses ESLint (configured in .eslintrc.yml) with the globals package for environment-aware linting. There are no pre-commit hooks or CI pipelines defined in the repository root.

The canonical issue tracker has moved — see GitHub issue #3726, which directs all new issues to https://github.com/uBlockOrigin/uBlock-issues/issues.


9. Integration Points

@gorhill/ubo-core Node.js API

The @gorhill/ubo-core npm package (v0.1.30) exposes the Static Net Filtering Engine for server-side use. It is a zero-dependency package requiring Node.js >= 18.

Entry point: platform/nodejs/index.js

Exports:

  • enableWASM() — Enable WASM acceleration for the engine
  • pslInit(raw) — Initialize the public suffix list from raw data
  • StaticNetFilteringEngine — The main engine class

StaticNetFilteringEngine Complete Reference

import { StaticNetFilteringEngine } from '@gorhill/ubo-core';

// Create a singleton instance (initializes public suffix list)
const engine = await StaticNetFilteringEngine.create({ noPSL: false });

// Load filter lists (resets engine state first)
await engine.useLists([
  { name: 'easylist', raw: '... filter list content ...' },
  { name: 'easyprivacy', raw: '... filter list content ...' },
]);

// Match a network request against loaded filters
// Returns: 0 (no match), 1 (block), 2 (allow/exception)
const result = engine.matchRequest({
  url: 'https://ads.example.com/tracker.js',
  originURL: 'https://www.example.com/',
  type: 'script',
});

// Check if last block result was flagged as "important"
const isImportant = engine.isBlockImportant();

// Get log data for the last match (for diagnostics)
const logData = engine.toLogData();

// Match and fetch modifiers (e.g., for CSP, redirect, removeparam)
const modifiers = engine.matchAndFetchModifiers(
  { url: '...', originURL: '...', type: '...' },
  'csp'  // modifier name
);

// Check if URL has filterable query parameters
const hasQuery = engine.hasQuery({
  url: 'https://example.com/page?utm_source=tracker',
  originURL: 'https://example.com/',
  type: 'main_frame',
});

// Filter (remove) tracked query parameters from URL
const filtered = engine.filterQuery({
  url: 'https://example.com/page?utm_source=tracker&id=123',
  originURL: 'https://example.com/',
  type: 'main_frame',
});
// filtered.url contains the cleaned URL

// Compile a filter list from text
const compiler = engine.createCompiler(parser);
const writer = /* ... */;
engine.compileList(
  { name: 'custom', raw: '||example.com^\n@@||example.com/allowed' },
  compiler,
  writer,
  {}  // options
);

// Serialize engine state (LZ4 compressed)
const serialized = engine.serialize();

// Restore engine from serialized state
engine.deserialize(serialized);

// Release the singleton instance
StaticNetFilteringEngine.release();

Note

The StaticNetFilteringEngine enforces a singleton pattern. Calling create() when an instance already exists throws "Only a single instance is supported.". Call release() before creating a new instance.

Match Result Codes

Code Meaning
0 No match — request is not affected by any filter rule
1 Block — request matches a blocking filter
2 Allow — request matches an exception (allowlist) filter

Request Type Values

The type field in matchRequest() corresponds to browser webRequest resource types: main_frame, sub_frame, stylesheet, script, image, font, object, xmlhttprequest, ping, csp_report, media, websocket, other.

WASM Acceleration API

import { enableWASM } from '@gorhill/ubo-core';

// Enable WASM before creating the engine
const wasmEnabled = await enableWASM();
// Returns true if WASM modules loaded successfully, false otherwise

// Then create the engine — it will use WASM-accelerated trie operations
const engine = await StaticNetFilteringEngine.create();

WASM modules are loaded from the same directory as the JavaScript modules. In the browser extension context, WASM loading is handled automatically by the platform adapter. In Node.js, enableWASM() must be called explicitly before engine creation.

Browser Extension Messaging Protocol

Content scripts communicate with the background page through the browser's extension messaging API, abstracted by vAPI:

Content script to background (vapi-client.js):

  • vAPI.messaging.send(channel, message) — Send a message to the background page on a named channel and await a response.

Background to content script (vapi-background.js):

  • Tab-targeted messaging for injecting cosmetic filter updates, scriptlet payloads, and configuration changes.

Message channels are used to route messages to the appropriate handler in the background page. Each UI page (popup, dashboard, logger) uses its own channel, and content scripts use dedicated channels for cosmetic filtering, scriptlet injection, and DOM surveying.

The platform-specific implementations (platform/chromium/webext.js, etc.) handle the differences between Chrome's callback-based API and Firefox's Promise-based API. The Chromium adapter (webext.js) wraps Chrome's callback-style chrome.* APIs in Promises for consistency with the Firefox browser.* API.


10. Troubleshooting

Error Messages Reference

The following error messages are defined in the uBlock Origin source code. Their presence in browser console logs indicates specific failure conditions:

Error Message Source File Cause & Resolution
"Pending useLists() operation" platform/nodejs/index.js:161 A useLists() call was made while a previous call is still in progress. Wait for the previous operation to complete before calling useLists() again.
"Only a single instance is supported." platform/nodejs/index.js:210 Attempted to create a second StaticNetFilteringEngine instance. Call StaticNetFilteringEngine.release() before creating a new instance.
"Failed to initialize public suffix list." platform/nodejs/index.js:268 The public suffix list could not be loaded during engine creation. Ensure the PSL data file is accessible, or pass { noPSL: true } to create() if PSL is not needed.
"Invalid µBlock.denseBase64 encoding" src/js/base64-custom.js:96 Corrupted or truncated base64-encoded data encountered during deserialization. This typically indicates corrupted extension storage. Clear the extension's storage and restart.
"Bad selfie" src/js/cosmetic-filtering.js:572, src/js/static-ext-filtering-db.js:232 Serialized engine state failed integrity validation. The engine will automatically recompile from raw filter lists. No manual intervention required unless the error recurs on every startup, which indicates persistent storage corruption.
"Unmatched bracket" src/js/regex-analyzer.js:184 A filter rule contains an invalid regular expression with an unmatched bracket. Check custom filter rules for syntax errors.

Tip

The "Bad selfie" error is recoverable — the engine rebuilds itself automatically. If you see this message once after an extension update, it is normal behavior. Persistent "Bad selfie" errors on every startup indicate corrupted extension storage that should be cleared.

Common Issues from GitHub

The following open issues in the gorhill/uBlock repository document known behaviors and long-standing feature requests:

Issue Status Summary
#3726 Open Issue tracker has moved to uBlockOrigin/uBlock-issues. New bugs should be filed there.
#3178 Open Feature request: way to disable blocking temporarily (per-tab or global).
#3604 Open Feature request: block sending Referrer information.
#3348 Open "Create" filter button disabled in element picker under certain conditions.
#2034 Open Show unused hostname(s) between a hostname and its base domain in the dynamic filtering matrix.
#2035 Open Ability to select hostname in element picker.
#2000 Open Element picker should not prevent page from receiving mouse events.
#614 Open Disable tooltip should be Cmd+click on Mac.
#544 Open Addressing inconsistencies in the dynamic rule matrix UI.

Tip

If you encounter a bug, file it at the current issue tracker: https://github.com/uBlockOrigin/uBlock-issues/issues (not the main repository).

Diagnostic Procedures

Extension not blocking requests:

  1. Check the popup panel — is uBlock Origin enabled for the current site? The power button toggles per-site filtering.
  2. Check trusted site directives — the site may be whitelisted via managed storage (toAdd.trustedSiteDirectives) or user settings.
  3. Open the network request logger (logger-ui.js) and reload the page. Examine whether requests are being matched.
  4. Verify filter lists are loaded — open the filter list management page (3p-filters.js) and check for compilation errors.

High memory usage:

  1. Check which filter lists are subscribed — more lists means more memory. Regional lists add significant overhead.
  2. Verify WASM is active (Firefox) — WASM trie implementations are more memory-efficient than the JS fallback.
  3. Check for excessive custom filter rules — large custom rule sets that do not benefit from trie compression (e.g., many regex-based rules) consume more memory per rule.

Extension storage corruption:

  1. Symptoms: persistent "Bad selfie" errors, filter lists failing to load, settings reverting.
  2. Export a backup of custom filters and settings via the dashboard.
  3. Clear the extension's storage (browser settings > extension details > clear data).
  4. Reimport the backup.
  5. For enterprise deployments, the managed storage configuration will be re-applied automatically after storage is cleared.