Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bot Detection Bundle Laravel Package

setono/bot-detection-bundle

A Symfony bundle for detecting bots and crawlers from request data, helping you filter automated traffic in analytics, block unwanted scrapers, and serve tailored responses to human visitors.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require setono/bot-detection-bundle

With Symfony Flex the bundle is auto-registered in config/bundles.php. Without Flex, add it manually:

// config/bundles.php
Setono\BotDetectionBundle\SetonoBotDetectionBundle::class => ['all' => true],

There is no required configuration — the bundle exists to expose an autowirable detector service, so config/packages/setono_bot_detection.yaml is typically unnecessary. Import the bundle's routes/config only if you actually need to override a service.

Where to look first

  1. vendor/setono/bot-detection-bundle/src/ — it is a small package; read the detector interface and its implementation before wiring anything. Confirm which underlying UA-matching library is pulled in (composer show setono/bot-detection-bundle --requires).
  2. bin/console debug:autowiring bot — this gives you the exact FQCN to type-hint in your installed version, instead of copying an interface name from a blog post.
  3. bin/console debug:container --show-private | grep -i bot — confirms the service id if you need to decorate or alias it.

First use case

The most common day-one use is "stop letting bots pollute my application logic" — analytics events, A/B test assignment, search indexing, recommendation counters. Inject the detector and branch:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// use Setono\BotDetectionBundle\...\BotDetectorInterface; // verify FQCN via debug:autowiring

final class LandingPageController
{
    public function __construct(private readonly BotDetectorInterface $botDetector)
    {
    }

    public function __invoke(Request $request): Response
    {
        $isBot = $this->botDetector->isBot($request->headers->get('User-Agent', ''));

        // e.g. don't assign an A/B variant to a crawler
        return $this->render('landing.html.twig', ['isBot' => $isBot]);
    }
}

Validate the install in ten seconds with a throwaway console command that runs a known Googlebot UA and a known Chrome UA through the detector. If both return the same value, check that the service you injected is the real implementation and not a stub.

Implementation Patterns

Tag the request once, in a listener. Don't call the detector from five different services. Add a kernel.request subscriber that sets a request attribute, then read it downstream:

final class BotFlagSubscriber implements EventSubscriberInterface
{
    public function __construct(private readonly BotDetectorInterface $botDetector) {}

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => ['onRequest', 100]];
    }

    public function onRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $ua = $event->getRequest()->headers->get('User-Agent', '');
        $event->getRequest()->attributes->set('_is_bot', $this->botDetector->isBot($ua));
    }
}

Gate expensive or stateful work. The classic trio: skip recommendation/recently-viewed writes, skip A/B variant persistence, skip server-side analytics hit dispatch. Guarding these is where the bundle pays for itself — bot traffic routinely dwarfs human traffic on public pages.

Combine with symfony/rate-limiter for a cheap bot-tuned limit. Give flagged UAs a materially tighter limiter, but keep it as a limit, not a block — see the gotchas.

SEO-safe allow-listing. Most teams need the inverse of "block bots": let Googlebot/Bingbot through to server-rendered content while treating everything else as a crawler. Build the allow-list yourself (the bundle is a boolean UA check, not a policy engine) and, for the search engines you actually care about, verify by reverse-DNS/IP rather than UA string.

Decorate for memoization and policy. If the detector runs in more than one listener, wrap it:

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;

#[AsDecorator(decorates: 'setono_bot_detection.bot_detector')] // confirm the id
final class CachingBotDetector implements BotDetectorInterface
{
    /** @var array<string, bool> */
    private array $cache = [];

    public function __construct(private readonly BotDetectorInterface $inner) {}

    public function isBot(string $userAgent): bool
    {
        return $this->cache[$userAgent] ??= $this->inner->isBot($userAgent);
    }
}

Decoration is the intended seam — you can also layer an internal-traffic allow-list or an office-IP bypass here without touching vendor code.

Logging and metrics. Add a Monolog processor that stamps is_bot onto every record, or increment a counter. This is exactly the telemetry you want when someone asks "why are our conversion numbers weird".

Deterministic tests. Bind a fake implementation in config/services_test.yaml so functional tests never depend on UA-string heuristics:

services:
    _defaults: { autowire: true, public: true }

    Setono\BotDetectionBundle\...\BotDetectorInterface:
        class: Tests\Fake\AlwaysHumanBotDetector

Unit-test your own policy code against the interface; leave the regex library's accuracy to the library.

Gotchas and Tips

  • This is a thin wrapper, and you should price it as one. With 8 stars and zero dependents there is no community safety net. The upside is that the source is a handful of files — read it before adopting, and be prepared to replace it with a direct dependency on the underlying UA-matcher if the bundle ever stalls. For a long-lived project, that is a one-file swap behind your own interface.
  • UA strings lie. Any client can send Googlebot/2.1. Never use this for authorization, access control, or deciding whether to serve sensitive data. Use it for analytics hygiene, cache behaviour, rate-limit tuning, and UX.
  • Detection rules are vendor-baked. The bundled bot list only refreshes when you composer update. New crawlers slip through on stale installs; add a periodic dependency-update review if bot detection is load-bearing for your metrics.
  • Empty User-Agent is the sharp edge. A missing UA usually returns false (human), and many scrapers send no UA at all. Decide your policy explicitly: if ($ua === '') { /* treat as bot / unknown */ } rather than inheriting the library default.
  • Cache interaction is the subtle bug. If you vary responses by bot status, Vary: User-Agent shatters your HTTP cache into thousands of entries. Prefer deciding before the response is stored (a Cache-Control/no-store decision), or branch in application code after the cached fragment is served.
  • Debugging misclassification. If the underlying detector exposes the matched signature (crawler-detect's getMatches() equivalent), log it alongside the UA. "False positive on a real user" is impossible to diagnose from a bare boolean; the matched regex plus the UA gets you a one-line bug report.
  • Memoize if you call it more than once. These are regex sweeps across a large signature list, run on every request, sometimes several times. The decorator above is cheap insurance.
  • Don't expect configuration. There is no meaningful blocklist/allowlist config tree to discover — if your requirement is "block these specific bots", you are writing that layer yourself on top of the boolean.
  • Check the constraint matrix before a framework upgrade. Small bundles lag on new major Symfony/PHP versions because there is no downstream pressure; verify composer.json constraints rather than assuming a recent release date implies compatibility with your target Symfony version.
  • Release cadence is not adoption. A recent release date on a zero-dependent package signals a maintainer keeping the lights on, not a battle-tested API surface. Pin the minor version and read the changelog on update.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console