omines/antispam-bundle
Omines Antispam Bundle adds configurable spam protection to Symfony forms with honeypot fields, time-based submission checks, blocked-word filtering, and IP/email validation—no CAPTCHA friction. Lightweight, framework-native, and simple to wire into existing form types.
omines/antispam-bundle is a Symfony bundle, not a Laravel package — install it knowing that first.
composer require omines/antispam-bundle
There is no service provider, no publishable config, no facade, and no Laravel integration. To use it in a Laravel app you either (a) pull it in as a standalone library and register the services yourself, or (b) read the source and lift the two Captcha\ classes you actually need.
Look first at:
src/Captcha/CaptchaInterface.php — the single-method contract (verify(Request $request)).src/Captcha/Honeypot.php — ~30 lines, trivially portable.src/Captcha/ReCaptcha.php — the reCAPTCHA v2/v3 verifier, uses Symfony's HTTP client and ReCaptchaResponse.src/SpamProtection.php — orchestrator that iterates over enabled captchas and calls verify().First use case (portable): copy Honeypot.php into app/Support/Antispam/Honeypot.php, drop the Symfony Request type-hint for Illuminate\Http\Request, and wrap it in a custom validation rule. You now have honeypot protection in a Laravel form without dragging in symfony/form or symfony/http-kernel.
Pattern 1 — port the honeypot as a Laravel rule. The honeypot logic is dependency-free. Extract the "check a named field is empty" behavior into an invokable rule (new HoneypotRule()) and apply it in a FormRequest:
class ContactRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email'],
'_gotcha' => [new HoneypotRule()],
];
}
}
Render the _gotcha field hidden via style="display:none" and tabindex="-1" — the bundle does not hide it for you.
Pattern 2 — vendor the reCAPTCHA verifier. ReCaptcha is a thin wrapper around POST https://www.google.com/recaptcha/api/siteverify. If you want the bundle's score-threshold checks and error handling (it distinguishes hostname mismatches, action mismatches, and score thresholds), instantiate it directly with Laravel's HTTP client substituted for Symfony's HttpClientInterface:
$captcha = new ReCaptcha(
secret: config('services.recaptcha.secret'),
siteKey: config('services.recaptcha.site'),
verifyUrl: 'https://www.google.com/recaptcha/api/siteverify',
httpClient: new SymfonyHttpClientAdapter(),
scoreThreshold: 0.5,
);
Then wrap in a validation rule or a SpamProtection service binding.
Pattern 3 — full Symfony-style usage (if you add symfony/form). Register the CaptchaType form field on an existing Symfony form and let the FormTypeExtension fire automatically. This is the intended path but rarely worth the dependency weight in a Laravel app.
Pattern 4 — driver-based switching. The bundle's config lets you toggle honeypot and captcha per environment. Mirror this in Laravel with a config/antispam.php and a SpamProtection::class singleton keyed off config('antispam.drivers'). Cheap to set up, keeps test environments deterministic (antispam.drivers => [] for feature tests).
config/antispam.php publish, no @antispam Blade directive, no facade. Every integration point is Symfony's container. Plan for a translation layer or extraction.symfony/form is a hard transitive requirement of the FormTypeExtension. If you only want honeypot/captcha verification, install the bundle with --no-install discipline: add it, read the source, remove it, and vendor what you need. Otherwise you inherit the full Symfony form component.ReCaptcha allows configuring both, but a misconfigured threshold (e.g. 0.0) silently passes all traffic. Log the returned score in development — the bundle does not surface it in exceptions by default.type="text" and only CSS-hide it, some bots still detect it. Use display:none and a plausible name (website, company) — the bundle does not enforce either.SpamProtection::validate() throws vs isValid() returns bool. In a Laravel validation rule you almost always want isValid() so the failure flows through $errors; throwing bypasses the validator's message bag.CaptchaInterface into the container — easier than intercepting HTTP. For honeypot, simply post a non-empty _gotcha field and assert the 422.CaptchaInterface and tag the service omines.antispam.captcha (Symfony) or bind it into a SpamProtection manager (Laravel). This is the cleanest way to add a custom challenge (Turnstile, hCaptcha) without forking.infection update, Symfony 7 deprecation fixes) — no new features or breaking changes, but also confirms the package is still maintained, which matters for a pre-1.0 dependency you're considering vendoring.How can I help you explore Laravel packages today?