core23/antispam-bundle
A Symfony bundle for stopping form spam with honeypots, submission timers, banned scripts, markup and phrases, plus URL counting. Configure reusable anti-spam profiles and apply them to any form. Compatible with PHP 8.2+ and Symfony 6.4, 7.x and 8.x.
First, resolve the stack mismatch — before anything else. Despite the core23/antispam-bundle coordinate and the "Laravel developer" framing, this is a Symfony bundle, not a Laravel package. It ships a Bundle class, a DependencyInjection extension, container tags, symfony/form extensions and event subscribers. There is no service provider, no publishable config, no middleware, no facade. composer require core23/antispam-bundle will pull the code, but nothing will wire itself into a Laravel app, and the form integration assumes a full Symfony kernel.
So "getting started" splits into two tracks:
If you actually run Symfony (6.4/7.4/8.x, PHP 8.2+):
composer require omines/antispam-bundle — note the README badges point at omines/, not core23/ or nucleos/. Confirm which coordinate is canonical and maintained (see Gotchas) before you pin.config/bundles.php.config/packages/antispam.yaml (honeypot + timer is the sane default to start).omines.github.io/antispam-bundle first; the README is a teaser, the real API surface (profile schema, per-mechanism options) lives there.If you are in Laravel (the realistic case): the artifact is not adoptable, but the design is. The highest-value first use case is a honeypot + signed-timestamp timer on registration and contact forms — that's ~50 lines as two custom validation rules plus a hidden field, with the same layered, profile-style config shape the bundle uses. Treat this as "adopt the pattern, not the package": read their mechanism list (honeypot, timer, banned scripts, banned markup, banned phrases, URL count), copy the configuration model, and implement against Laravel's FormRequest/validator pipeline. The bundle's real contribution to you is a checklist of heuristics that are already proven-ish, so you're not inventing policy from scratch.
Profile-per-form is the core idea, and it's the part worth stealing. The bundle's value proposition is that you don't sprinkle checks in controllers — you declare a named profile once and attach it to specific forms. Mirror that in Laravel with a config array:
// config/antispam.php
return [
'profiles' => [
'registration' => ['honeypot' => true, 'timer' => ['min' => 3, 'max' => 3600]],
'contact' => ['honeypot' => true, 'timer' => ['min' => 2], 'urls' => ['max' => 3]],
'checkout' => ['honeypot' => true], // invisible to real users; no timing
],
];
Then a single AntiSpam service resolves the profile and applies it, so adding a mechanism is a config edit, not a code change.
Layer by friction budget, not uniformly. The bundle deliberately separates mechanisms so you can dial friction per funnel. Match mechanisms to blast radius:
Wire it as validation, not a controller if. In Symfony the mechanisms surface as form constraints/events; in Laravel the equivalent is custom validation rules (Honeypot, SubmittedTooFast) on the FormRequest. That keeps the anti-spam outcome visible in the same place as all other validation, testable, and consistent in error messaging.
Keep an abstraction boundary. Because lineage and maintenance are uncertain, wrap whatever you adopt behind your own interface (AntiSpam::check(Profile $p, Request $r)) so you can swap the bundle for spatie/laravel-honeypot, Turnstile, or edge bot management without touching product code. This is the single most important implementation pattern given the maturity signals.
Instrument before you trust it. Log every rejection with the mechanism that fired and the profile. This is how you tune thresholds and catch false positives — the bundle won't do this for you, and heuristics rot silently without it.
core23), repository (nucleos/NucleosAntiSpamBundle), and README (omines/antispam-bundle) all disagree. This looks like a fork-of-a-fork with an unchanged README. Before pinning anything, confirm the canonical, maintained upstream (likely omines/) and whether this fork carries unmerged fixes or drift. Pin an exact version and read the diff.type="hidden". Bots skip type=hidden inputs; a field hidden via position: absolute; left: -9999px (or display:none with a real-looking name like website) is what actually baits them. If you're reimplementing, get this detail right — it's the whole point.min low (2–3s), and always set a max upper bound or the field becomes a silent rot mechanism. Exclude known-good flows (returning users, autofill) where you can.cache:clear / config:cache after profile edits, or you'll debug a change that "isn't taking effect."How can I help you explore Laravel packages today?