Ad blockers get described as "things that remove ads", but in practice two separate layers are at work: one stops the request itself, the other hides whatever is left on screen. Once you separate the two, filter syntax makes sense, and so do the strengths and limits of extensions, DNS blocking and device apps. This article walks through the syntax and then the choice.
Two layers, not one
The first is the network layer. It inspects the addresses a page tries to load and stops those used for ad delivery or tracking. If the request never happens, no image and no script is fetched: bandwidth drops and tracking scripts never run. This is where most of the effect comes from.
The second is the display layer. Anything the network layer could not stop — or the empty frame it left behind — is hidden with CSS. When ads are served from the same domain as the article, blocking the request would take the article with it, so this layer handles those cases.
Filter syntax maps directly onto the two.
// network layer: block requests to ads.example.com and its subdomains, but only from another domain ||ads.example.com^$third-party // display layer: on news.example.jp, hide elements with class="ad-banner" news.example.jp##.ad-banner
What the symbols mean
This "Adblock style" syntax is largely shared by uBlock Origin, AdGuard and Adblock Plus. There are not many symbols to learn.
| Symbol | Meaning | Example |
|---|---|---|
|| | Start of a domain, subdomains included | ||ads.example.com^ |
^ | A separator character (/, ?, : …) or the end | ||example.com^ |
* | Any text | /banner-*.gif |
@@ | Exception: never block what this matches | @@||example.jp/assets/* |
$ | Everything after it is a condition | $third-party,script |
## | Hide elements (a CSS selector follows) | example.com##.ad |
#@# | Cancel a hiding rule | example.com#@#.ad |
! | Comment line | ! my own note |
The trailing ^ in particular changes the outcome. Written as ||ads.example.com, the rule matches any domain starting with that text, which sweeps in look-alikes such as ads.example.com.evil.net. To end the domain there, write ||ads.example.com^.
$third-party means "only when loaded from another domain", $script means "only for scripts",
and $domain=example.com means "only while that page is open".
A broad pattern with no conditions will take necessary requests with it.
Where it runs decides what it can do
| Approach | Reach | Can do | Limits |
|---|---|---|---|
| Browser extension | That browser only | Both request blocking and element hiding; sees inside the page, so it can be precise | No effect on other apps; bound by what the extension platform allows |
| DNS blocking | Every app on a device or router | Configured once; reaches ads inside apps too | Whole domains only; cannot hide elements |
| Device app (VPN-shaped) | The whole device | Works across apps | It sits in the middle of your traffic, so vendor trust is the deciding factor |
The limit of DNS blocking is the part most often misunderstood. It can only act on a domain as a whole, so an ad served from the same domain as the article cannot be stopped — blocking it would remove the article too. When an ad disappears with an extension but survives DNS blocking, this is usually why.
On the extension side, the platform is shifting from "the extension decides on each request" to "the extension hands the browser a declared table of rules and the browser applies it". The declarative form is faster and keeps request contents out of the extension's hands, but it caps how many rules and which conditions you can express. Check the extension's own documentation to see which model your browser and extension use today.
A wrong filter breaks pages quietly
A mistaken filter does not raise an error. It simply fails to match, or matches too much — and both surface in ways that are hard to notice. Here are three common ones, as reported by our filter syntax checker.
// input * http://ads.example.com/ ||ads.example.com ##div // output (abridged) * … Warning: This matches every request. It will almost certainly break pages. http://ads.example.com/ … Warning: Writing http:// misses the https:// version. ||ads.example.com … Warning: Without a trailing ^ this can also match look-alike domains. ##div … Warning: Hiding a bare tag name with no domain removes that element on every site.
Every one of these is syntactically valid, so the extension accepts them without complaint. The reliable way to check your own filters is to read each line back as a plain sentence.
Choosing, in practice
- Decide the reach first. Whether the browser is enough, or you also want ads inside phone apps, points to a different approach.
- Do not pile on filter lists. Every list adds matching work on each page load. One or two well-maintained lists usually feel lighter than five overlapping ones.
- Plan the allow list. Adding the sites you want to keep reading up front avoids the "the layout broke, turn everything off" reflex.
- Check that the list is still maintained. Filter lists track changes on the ad side; a stale list adds work without adding effect.
From the site owner's side
Widespread ad blocking directly reduces income for sites funded by advertising. At the same time, readers choosing how pages look on their own devices is not, in itself, a problem. The workable middle is to make it easy for a reader to decide "this site gets allow-listed": keep pages light, cut down on tracking, and offer a way to support the site.
One technical note: if measurement scripts are entangled with rendering, the page stops working the moment they are blocked. Keep the article readable when analytics fail and blocked visitors still get what they came for.
Frequently asked questions
What does an ad blocker actually do?
It works in two layers. The network layer stops the request to an ad or tracking address before anything is fetched. The display layer hides, with CSS, whatever survived that or left an empty frame behind. Filter syntax mirrors this split: a line like ||example.com^ belongs to the network layer, while example.com##.ad-banner belongs to the display layer. Together they produce the impression that the ads have disappeared.
Should I use an extension, DNS blocking, or a device app?
They differ in reach and precision. A browser extension sees inside the page, so it can hide individual elements, but it only works in that browser. DNS blocking applies to every app on a device or router, yet it can only act on whole domains, so blocking a domain that also serves the article body leaves you with nothing. Device apps (often VPN-shaped) reach the widest, but because they sit in the middle of your traffic, how much you trust the vendor becomes the main question.
What happens when a filter is written wrong?
Pages break, quietly. Common cases: dropping the trailing ^ in ||example.com so look-alike domains are caught too; writing http:// so the https:// request never matches; and hiding a bare tag such as ##div with no domain, which removes that element on every site. Our filter syntax checker flags all three as you type.
Is it acceptable to use an ad blocker?
Changing how pages look on your own device is generally fine, though it does thin the ad revenue that keeps many sites running. A practical compromise is to allow-list the sites you want to keep reading and support them where a mechanism exists. Technically, note that every added filter list means more matching work on each page load, so piling lists on can make browsing feel slower rather than faster.