AntiAdBlock.Core

Implementation

7 min readBy the AntiAdBlock Core team

Anti-adblock code: examples, common pitfalls and what actually holds up

Every publisher's first anti-adblock code is a bait div or a fetch probe. Here is what each does, why it breaks, and what a real implementation that survives modern blockers looks like.

The bait div, the most common anti-adblock code

The most widely copied anti-adblock code is the bait div: a DOM element with an ad-like class name, checked after a short delay to see whether the blocker hid or removed it.

We deliberately do not publish a runnable snippet here. The method itself is no secret: create an invisible element whose class name matches an ad-related entry on a public filter list such as EasyList, wait a moment, then check whether a blocker hid it. The problem is exactly that it is public and static. A fixed, well-known class name is the first thing a filter-list maintainer copies into a rule, and from then on the check quietly returns 'no blocker' for every visitor who carries that list.

This works against blockers that apply cosmetic filtering, hiding elements by class selector, because those blockers will hide the bait and the check fires. For a basic blog wanting a rough count, it is a reasonable first probe. But it fails silently in more cases than it catches, which is the dangerous kind of failure.

Fetch probes and script injection checks

A second common pattern is the fetch probe: loading a URL that looks like an ad network resource and treating a network failure as evidence of a blocker.

Again, we do not ship a runnable probe here. The idea is to request a path that looks ad-related and treat a failure as a blocker, which catches network-level blockers that a cosmetic check misses. In practice it is fragile: the probe path is another fixed, listable signature, a plain 404 or a corporate firewall looks identical to a real block and produces false positives, and under Manifest V3 the request is stopped in the browser engine before your code runs, so the signal is easy to misread.

Fetch probes reach blockers that operate at the network layer. But the false-positive rate is meaningful: a slow connection, a CDN glitch or a browser security policy can all trigger the catch branch without any ad blocker present. And if the specific URL you probe falls off the filter list, the probe returns false negatives forever while appearing to still run.

Why static anti-adblock code gets filter-listed

Every pattern shown above shares the same structural weakness: it is static. The class name adsbox, the URL pagead2.googlesyndication.com, the variable name detectAdBlock, anything fixed and public is something a filter-list maintainer can copy into a rule. EasyList, uBlock Origin and AdGuard supplementary lists all contain rules targeting known anti-adblock code patterns.

Once your bait class or probe URL is on a filter list, the evasion is permanent until you change it. The blocker either hides the bait in a way the code does not recognise as hiding, ignores the element entirely, or blocks the probe URL before the fetch resolves. The code still runs, still reports a result, but the result is always negative, regardless of whether a blocker is present.

The fix is to never use a fixed, recognisable value as a detection signal. Bait class names should be generated at runtime from a random prefix. Probe URLs should be hosted on your own origin, not a public ad-network endpoint, and should serve a known asset whose absence is the signal rather than a request to a flagged external domain.

What Manifest V3 breaks and what survives

Chrome's Manifest V3 replaced dynamic webRequest blocking with declarativeNetRequest, where the browser enforces a fixed rule set at the network layer. uBlock Origin Lite is built entirely on this model. From the page's JavaScript perspective, blocking happens below any observable event: no onerror fires, no rejected promise, no DOM mutation, just a request that resolves instantaneously with no data.

A bait div check will not detect a declarativeNetRequest blocker if the blocker uses network rules rather than cosmetic filters for the bait path you chose. A fetch probe will not resolve to catch() because the block does not cause a JavaScript-visible network error, it returns a response with a synthetic empty body. Both patterns fail on this class of blocker, which is now the fastest-growing segment.

What does survive MV3 is timing. A request blocked natively by declarativeNetRequest resolves on a different time curve than a genuine network error or a real asset load. Measuring that timing difference across multiple probe requests, combined with a check for noop-redirect payloads, gives a signal unique to MV3 blockers that survives their lack of JavaScript-visible side effects.

The what an anti-adblock script is guide covers which specific signals survive MV3 in practice and how to combine them into an ensemble that does not depend on any single blocker behaviour. If you are evaluating whether to build or buy the full stack, the adblock killer comparison lays out that decision clearly.

What robust anti-adblock code architecture requires

Detection that holds up needs properties no single snippet can provide, and building them yourself is a standing engineering commitment rather than a one-off paste. It has to combine several independent checks so that no single filter rule takes the whole thing down at once, and it has to keep working as the lists change.

It also has to present no fixed, public signature for a filter list to capture, because anything static and recognisable gets listed and then quietly stops working. Keeping that surface unpredictable on every page load is not something a copy-paste snippet does.

Third, maintenance. Anti-adblock code is not write-once software. Filter lists update daily, new blocker versions ship, and new scriptlet countermeasures appear regularly. A snippet you paste today will detect a shrinking fraction of blockers next month without a single visible error. That ongoing upkeep is the real cost, which is why most publishers are better off pointing a single line of code at a maintained service like AntiAdBlock Core than owning the arms race themselves.

Before you settle on any approach, it is worth knowing how ad blockers bypass detection scripts, because that is what decides how long your code keeps working.

Frequently asked questions

What is the simplest anti-adblock code that still works in 2026?

A combination of a randomised bait div (class name generated at runtime) and a self-hosted fetch probe covers both cosmetic-filtering and network-blocking blockers. Neither alone is reliable against modern blockers; together they handle more cases. Neither catches MV3 blockers accurately without a timing fingerprint component.

Why does anti-adblock JavaScript stop detecting over time?

Static class names, fixed probe URLs and known variable names get added to ad-blocker filter lists. Once listed, the blocker bypasses the specific check permanently. Detection rates fall without any visible error, the code runs but reports false negatives. Rotating bait values and updating probe logic against live filter-list changes is what keeps detection accurate.

Can I write and maintain my own anti-adblock code long-term?

Yes, but the real cost is ongoing maintenance, not the initial code. Filter lists change daily, Manifest V3 blockers need timing-fingerprint probes, and new scriptlet countermeasures appear regularly. For publishers without a dedicated ad-tech team, a managed detection service that handles the update cycle is usually more economical than keeping a self-hosted snippet current.

Put the best adblock killer script to the test.

Free up to 10,000 detections per month. 60-second install.

Keep reading

Explore more