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.
const bait = document.createElement('div');
bait.className = 'adsbox ad-banner';
bait.style.cssText = 'height:1px;width:1px;position:absolute;';
document.body.appendChild(bait);
setTimeout(() => {
const hidden =
bait.offsetHeight === 0 ||
bait.offsetWidth === 0 ||
!document.body.contains(bait);
document.body.removeChild(bait);
if (hidden) console.log('Ad blocker detected');
}, 150);
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.
// Fetch probe, catches network-level blocking
fetch('https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js', {
method: 'HEAD',
cache: 'no-store',
mode: 'no-cors',
}).then(() => {
// Request succeeded, ad blocker likely absent
}).catch(() => {
// Request failed, likely blocked by ad blocker
});
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 anti-adblock script 2026 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 script comparison lays out that decision clearly.
What robust anti-adblock code architecture requires
Robust anti-adblock code has three properties that no single-snippet approach can provide. First, multiple independent vectors: combining a DOM bait check, a network fetch probe and a timing fingerprint means no single filter rule or scriptlet can defeat all three at once. If one vector is neutralised, the others continue and the combined verdict stays accurate.
Second, randomisation. Bait class names, probe paths and variable identifiers should differ on every page load. Generating them from a cryptographic random prefix at initialisation time removes any fixed target for a filter rule to match. A detector that looks different on every request cannot be permanently listed.
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. AntiAdBlock Core runs 11 voting vectors retrained nightly against live filter-list deltas, because the adversarial upkeep is the real cost of doing this correctly, and moving it to a specialist is usually the better economics for a publisher without a dedicated ad-tech team.
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.