This page covers how the classic uBlock Origin (MV2) extension is assembled for Chromium, Firefox, Opera, and Thunderbird: the Makefile targets, the per-platform shell scripts, the differences between platform manifests, and the publishing scripts that push build artifacts to browser stores.
For the GitHub Actions workflow that runs this process automatically on tag creation, see CI/CD Pipeline. For the MV3/uBOLite build process, see MV3 Build Process and Ruleset Generation. For how the version string is structured and how self-hosted update manifests are distributed, see Extension Update System. For a comparison of the manifest files themselves in depth, see Extension Manifests and Platform Support.
Every MV2 build draws from three source trees, tracked as the sources and platform Makefile variables:
| Variable | Glob | Contents |
|---|---|---|
sources | ./dist/version, ./assets/**, ./src/** | Version file, filter list assets, shared JS source |
platform | platform/*/* | Per-browser adapters, manifests, and vAPI implementations |
assets | dist/build/uAssets | External filter list assets fetched by tools/pull-assets.sh |
The single-line dist/version file (currently 1.69.1.5) is the authoritative version string for all build artifacts.
Sources: Makefile11-13 dist/version1
The Makefile defines one target per MV2 browser, each depending on its shell script plus all source and asset files.
MV2 build target overview:
| Makefile target | Script invoked | Output directory |
|---|---|---|
chromium | tools/make-chromium.sh | dist/build/uBlock0.chromium |
opera | tools/make-opera.sh | dist/build/uBlock0.opera |
firefox | tools/make-firefox.sh all | dist/build/uBlock0.firefox |
The default all target builds chromium, firefox, and npm (the Node.js package), but not opera — Opera must be built explicitly.
Sources: Makefile24-47
Diagram: MV2 Build Pipeline (inputs → scripts → artifacts)
Sources: Makefile11-47 .github/workflows/main.yml22-32
In CI, the build scripts are invoked with the version tag as an argument (e.g. tools/make-chromium.sh 1.69.1.5). Locally, invoking without arguments causes each script to read dist/version directly.
The CI workflow only builds Chromium and Firefox artifacts for release. Opera is not included in the automated CI build.
Sources: .github/workflows/main.yml28-32
Each shell script follows the same general pattern:
dist/version).dist/build/.src/ into the build directory.platform/<browser>/ (including manifest.json).manifest.json.The Firefox script additionally accepts an all argument, which causes it to also produce a signed XPI for the self-hosted update channel alongside the standard build.
Sources: Makefile26-43 .github/workflows/main.yml28-32
Each browser platform has its own manifest.json under platform/<browser>/. These are the starting point for each build; the build script updates the "version" field at build time.
Diagram: Platform Manifests and their Key Differences
Sources: platform/chromium/manifest.json1-118 platform/firefox/manifest.json1-138 platform/opera/manifest.json1-119 platform/thunderbird/manifest.json1-94
| Field | Chromium | Firefox | Opera | Thunderbird |
|---|---|---|---|---|
manifest_version | 2 | 2 | 2 | 2 |
| Min browser version | Chrome 93 | Firefox/Gecko 115 | Opera 79 | Thunderbird 91 |
browser_specific_settings | — | ✓ (gecko, gecko_android) | — | ✓ (gecko) |
options_ui / options_page | options_ui | options_ui | options_page | options_ui |
sidebar_action | — | ✓ (logger-ui.html) | ✓ (logger-ui.html) | — |
contextMenus permission | ✓ | — | ✓ | — |
menus permission | — | ✓ | — | — |
dns permission | — | ✓ | — | — |
privacy permission | ✓ | ✓ | ✓ | ✓ |
content_security_policy | ✓ | — | — | — |
incognito: split | ✓ | — | ✓ | — |
file:// content_script match | — | ✓ | — | ✓ |
storage.managed_schema | ✓ | — | — | — |
| Icon format | PNG | SVG | PNG | SVG |
dns permission enables CNAME-uncloaking support, which is a Firefox-only API. The extension ID is [email protected] (declared in browser_specific_settings.gecko.id). A data_collection_permissions field with "required": ["none"] is required by Mozilla policy.options_page key (not options_ui). Also adds a sidebar_action pointing to logger-ui.html, making the logger available as a panel.browser_specific_settings structure with Firefox but requires a lower minimum version (91.0). Lacks keyboard command declarations and the contextMenus permission.content_security_policy and storage.managed_schema (for enterprise policy support via managed_storage.json). Also the only one to explicitly set incognito: "split" to separate incognito from normal profiles.Sources: platform/chromium/manifest.json81-118 platform/firefox/manifest.json17-27 platform/opera/manifest.json91-118 platform/thunderbird/manifest.json1-10
All four platform manifests declare the same three groups of content scripts:
| Script group | Matches | Run at | Purpose |
|---|---|---|---|
vapi.js, vapi-client.js, contentscript.js | http://*/*, https://*/* (+ file:// on Firefox/Thunderbird) | document_start | Core content filtering and DOM manipulation |
js/scriptlets/subscriber.js | Known filter list hosting sites (easylist.to, github.com, etc.) | document_idle | Handles abp:subscribe / ubo:subscribe link clicks |
js/scriptlets/updater.js | uBlockOrigin GitHub and Reddit pages | document_idle | In-page update notifications |
The Firefox manifest also matches file://*/* for the primary content script group, allowing the extension to filter locally opened HTML files.
Sources: platform/chromium/manifest.json38-79 platform/firefox/manifest.json54-97
After building, separate Node.js scripts under publish-extension/ submit the artifacts to browser stores. These are invoked via Makefile targets and are not run as part of CI.
Diagram: Publish Makefile Targets and Scripts
Sources: Makefile102-171
| Makefile target | Script | ghasset | channel | Destination |
|---|---|---|---|---|
publish-chromium | publish-chromium.js | chromium | — | Chrome Web Store (stable) |
publish-edge | publish-edge.js | chromium | — | Edge Add-ons (uses chromium zip) |
publish-firefox | publish-firefox.js | firefox | listed | AMO public listing |
publish-dev-chromium | publish-chromium.js | chromium | — | Chrome Web Store (dev extension) |
publish-dev-firefox | publish-firefox.js | firefox | unlisted | AMO unlisted (dev channel) |
upload-firefox | upload-firefox.js | firefox | listed | AMO upload (signing step) |
upload-dev-firefox | upload-firefox.js | firefox | unlisted | Self-hosted (dist/firefox/updates.json) |
All publish targets receive ghowner=gorhill, ghrepo=uBlock, and a ghtag argument specifying the release tag from which to download the pre-built artifact. The scripts fetch the artifact from the GitHub release rather than building locally.
The Edge publish uses ghasset=chromium because the Edge extension is the same Chromium artifact — there is no separate Opera or Edge build script.
The upload-dev-firefox target passes updatepath=./dist/firefox/updates.json, which instructs upload-firefox.js to write an update manifest suitable for self-hosted distribution (see Extension Update System).
Sources: Makefile102-172
| Concern | CI (.github/workflows/main.yml) | Local (make) |
|---|---|---|
| Trigger | Tag push to master | Manual make chromium, make firefox, etc. |
| Version | ${GITHUB_REF/refs/tags//} (the tag name) | Read from dist/version |
| Asset fetch | tools/pull-assets.sh step | Requires dist/build/uAssets to exist |
| Platforms built | Chromium, Firefox only | Any target individually, or make all |
| Artifact names | uBlock0_VERSION.chromium.zip, uBlock0_VERSION.firefox.xpi | dist/build/uBlock0.chromium/, dist/build/uBlock0.firefox/ |
| Release creation | GitHub draft release via softprops/action-gh-release | No release automation |
| Publishing | Not performed in CI — separate manual make publish-* calls | Same |
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.