Free · no editions, no licensing code
Bad bots tell you what they are
You cannot block a scraper by its user agent, because a user agent is a string it picked. You can block it for what it did. Black Hole puts one path off limits in robots.txt, points an invisible link at that path, and blocks by address anything that follows the link anyway.
Two commands, then nothing
Install it, let it write the robots.txt directives, and the hidden link is already on every front-end page. There is no third step.
composer require justinholtweb/craft-blackhole
php craft plugin/install blackhole
# The one thing the plugin cannot do for you:
php craft blackhole/robots/write
# Black Hole for bad bots — do not remove
# User-agent: *
# Disallow: /blackhole
Features
Everything the WordPress original charges for, and the verification it does not do.
A link no reader can reach
Hidden, nofollow, aria-hidden and untabbable, spliced in before the last closing body tag — or placed wherever you want it with one Twig tag.
- A screen reader never announces it
- A keyboard user cannot tab into it
Blocked before routing
One indexed ban lookup at EVENT_BEFORE_REQUEST. A banned address gets a status code and a message — no templates rendered, no queries, no session started.
- The session is never touched until a ban is certain
- The check fails open; it cannot take a site down
A spoofed Googlebot does not walk through
Crawlers claiming a name that publishes reverse DNS are made to prove it — address to hostname, hostname to the operator's domain, and back to the same address.
- Anyone can point a PTR record at googlebot.com
- Nobody can make Google's own DNS agree
Allowlists that understand addresses
Exact addresses, dotted prefixes and CIDR ranges, IPv4 and IPv6 alike, compared on packed bytes — plus about forty crawler user agents out of the box.
- 2001:db8::1 and its expanded form are the same address
- Logged-in users and the control panel are never caught
A ledger, not a log file
One row per address — filterable, searchable, sortable — opening onto hostname, user agent, referrer, what it asked for and its whole request history.
- Release, whitelist, delete, or block an address by hand
- Every command the control panel has, the console has too
Yours to tune
Trip thresholds, ban lengths, a custom trap path, your own blocked and caught templates, an off-site redirect, email alerts and a retention window.
- All of it free — there is no paid tier to upgrade to
- Alerts latch per address, so a flood cannot flood your inbox
Frequently Asked Questions
The questions worth answering before you install it.
Yes. No editions, no licensing code, no trial. Everything the WordPress original's paid tier charges for — hit thresholds, a custom trigger path, per-bot hit logs, a redirect option, disabling for logged-in users — is here at no cost.
It should not, and the design goes out of its way to make sure. The link is display:none, aria-hidden and tabindex="-1", so a screen reader never announces it and a keyboard user cannot walk into it. A honeypot that catches people is a bug. The realistic risk is a link-prefetching browser extension on a staff machine, which is why never catching logged-in users is on by default.
Not if your robots.txt has the directives, because Google honours Disallow. That is the one setup step the plugin cannot do for you, and the control panel nags until it can see the rule in your live file. Something arriving with Googlebot's user agent that fails forward-confirmed reverse DNS is not Google, and gets treated accordingly.
Because a user agent is a string the client picked and a request is a fact. Blocking on the claim means blocking whatever the next scraper decides to call itself. The only place claims are considered at all is the allowlist — which is exactly why the allowlist verifies them.
Partly, and honestly. The trap, the ledger, the alerts and the console all work exactly as documented. What a full-page cache breaks is the blocking of already-cached pages — the cache answers before Craft boots, so the guard never runs for that request. The trap URL and the block page are themselves no-store and never cached. If you need the ban enforced ahead of the cache, feed the blocked addresses to whatever sits in front.
No. Every front-end request costs one indexed lookup against the ban list, and that is all — before routing, before templates, before the session. The session is deliberately not touched until an address is already banned, because asking whether someone is logged in starts a session, and a Set-Cookie on every response is a full-page cache that never hits again. DNS verification only ever runs on a catch, and its results are cached for a day.
Yes, to anything routable that is not inside the control panel. Re-run php craft blackhole/robots/write afterwards and remove the stale directive for the old path — the settings screen shows you what your file says versus what it should say.
Yes. The trap URL is per-site, so each site's pages carry a link to their own. The ban list is shared, because an address is an address regardless of which of your sites it was rude to.
No. There are no outbound HTTP requests anywhere in the plugin. The only network call it makes at all is DNS, and only on a catch.
Craft CMS 5.3+, PHP 8.2+. No runtime dependencies beyond Craft's own, and no build step.
Put one path off limits and see who ignores it
Free, for Craft CMS 5.3 and later. Install it, run one command, and the trap is live on every page.