A fork of uBlock Origin that adds !#define macro support for domain list expansion in filter lists.
The purpose of this fork is to implement uBlock-issues#330.
In 2018 this was a convenience request. In 2024–25 it's a countermeasure. Link shortener, file hosting and video hosting sites now buy 1–2 domains per day specifically to outpace static filter updates. The only sustainable response is a domain group you update in one place. Maintaining 20+ filter lines that each repeat a 10-domain prefix, where that list changes daily, is not a viable workflow.
Element Picker - Add Current Domain To Define Lines Screenshot
!#definedomain macros, expanded at parse time- Nested macros, with circular-reference protection
- Negation with
~Macro - Macros usable in cosmetic filters, network filters and filter options
- Red directive highlighting in the filter editor
- Auto-rename of macro usages when a
!#definename is edited - Define search panel in the element picker, with one-tap Add Current
- Mobile (Gboard) fixes for select-all, copy and cut in the filter editor
- Auto-updating Firefox releases
- Daily upstream sync
!#define MacroName (domain1.com,domain2.com,domain3.*)- The macro name can be anything without spaces.
- Domains are comma-separated inside
( ). - The definition must be on one line and must not contain
)inside the list. - Text after the closing
)is ignored, so it can hold a note. - Wildcards like
gplinks.*are supported. - The
!#defineline is removed from the list at parse time. - A macro applies only inside the list that defines it.
- Names match whole tokens only. A macro named like a real domain replaces that domain wherever it appears in the list.
!#define GplinksPartners.com (powergam.online,qrixpe.com,sanadegreecollege.in)
GplinksPartners.com##center, .main-content, .myTimerDiv
GplinksPartners.com##+js(set, document.hidden, false)
*$script,1p,domain=GplinksPartners.comAt parse time GplinksPartners.com expands into all three domains, so each rule is written once.
| Position | Result |
|---|---|
Before ##, #@#, #?#, #$#, #%# |
Joined with , in the same line |
domain=, from=, to=, denyallow= option values |
Joined with | in the same line |
| Start of a network filter | One line per domain |
Comment lines (!) are never expanded.
Prefix a macro with ~ inside a domain list or option to negate every domain in it:
*$script,domain=~GplinksPartners.com!#define Gplinks (gplinks.*,get2.in)
!#define GplinksAll (powergam.online,qrixpe.com,Gplinks)
GplinksAll##.ad-bannerDuplicates are removed. A macro that references itself, directly or through another macro, resolves to nothing for the looping part. An empty macro is not expanded.
!#define, !#if, !#else, !#endif and !#include are highlighted in red in the filter editor (#cc0000 light, #ff6b6b dark).
When you edit the name on a !#define line, all usages of that macro in the same list are renamed automatically. Undo and redo do not trigger a rename.
On Android, Gboard reads select-all, copy and cut from the editor DOM text. Stock CodeMirror 5 put line numbers and extra blank lines into that text. This fork:
- Renders line numbers and lint messages as CSS generated content instead of text.
- Uses inline elements for gutter helpers.
- Renders the whole document for lists up to 5000 lines, so select-all covers everything and not only the visible part.
Copied text is now exactly one line per filter line.
The picker has a search box for the !#define lines in My filters.
- Type part of a macro name (case-insensitive).
- Tap Add Current on the match.
- The current page's hostname is appended to that macro, My filters is saved, all filters are reloaded and the picker closes.
Button states: Added if the domain is already in the macro, Not found if the macro no longer exists. Punycode hostnames are converted to Unicode before being added.
build.yml runs on every push and pull request. It checks out upstream gorhill/uBlock, applies apply_patches.py and patch.py, then runs make firefox. Download the result from Actions → latest run → uBlockOrigin Define V Fork Unsigned.
Run the Release XPI workflow manually. It:
- Builds
uBlockDefine.firefox.xpifrom this repo. - Creates release
v<version>. If that release already exists, it is skipped. - Sets
update_urlin the manifest. Set the repository variableADDON_IDto override the add-on ID. - Publishes
updates.jsonto theupdatesrelease, so installed copies update themselves (Firefox 115+).
For a signed build, head to the Release section.
sync-upstream.yml runs daily at 03:17 UTC. It merges gorhill/uBlock master into this repo, keeps this README and the fork's workflows, then triggers Release XPI.
This fork does not maintain hand-edited copies of uBlock Origin's files. Two scripts apply string-based patches at build time:
| Script | Patches |
|---|---|
apply_patches.py |
!#define parser and expansion, directive color, auto-rename, element picker search |
patch.py |
CodeMirror mobile input fixes |
Both scripts are idempotent: a patch already present is skipped. If an upstream change breaks a patch anchor (it must match exactly once), the build fails with a descriptive error. Touched JS files are syntax-checked with node --check.
uBlock Origin by Raymond Hill — GPLv3
