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

Livewire Honeypot Laravel Package

blendbyte/livewire-honeypot

Blendbyte Livewire Honeypot provides lightweight, CAPTCHA-free spam protection for Laravel Livewire forms. It uses hidden honeypot fields and timing checks to catch automated submissions, helping keep forms simple, accessible, and user-friendly for real visitors.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. composer require blendbyte/livewire-honeypot
  2. Optionally publish the config: php artisan vendor:publish --tag=honeypot-config, then read config/honeypot.php first — it is the single place that governs fill-time thresholds, expiry, and logging. Most "why did this get flagged?" questions are answered there.
  3. Drop the component into an existing form view before the submit button:
<form wire:submit="submit">
    <input type="text" wire:model="name">
    ...
    <x-honeypot />
    <button type="submit">Send</button>
</form>
  1. First use case: a public contact form. Submit it once as a human (should pass), then fill the bait field via devtools and submit (should be rejected with no side effects on your app data). That two-minute loop is also the fastest way to confirm the package is actually wired up.

Before upgrading to 2.2.0, read docs/upgrading.md#upgrading-to-22. If you maintain a browser test suite, budget time for the selector change described below — it is the one thing in this release that will break CI rather than production.

Implementation Patterns

Livewire forms (the default path). Add <x-honeypot /> inside the component's Blade view and keep the validation/submit() logic unchanged. The package rejects silently — no exception to catch, no request fired — so your normal success path is untouched.

Plain HTML forms (new in 2.2.0). <x-honeypot /> now works outside Livewire, posting to a controller with a signed token. See docs/plain-forms.md. This is the pattern to reach for when you have a marketing-site form, a Laravel Breeze/Fortify-style login page, or any Blade form that posts without a Livewire component behind it. Verify on the server in the controller (or via middleware) before persisting.

Per-form tuning. Don't reach for one global threshold. A login form and a long application form have completely different legitimate fill times. Publish the config, keep the default for auth/contact, and override HONEYPOT_MAXIMUM_FILL_SECONDS (or disable expiry with 0) for the slow ones: multi-step wizards, checkout with an address lookup, anything with file uploads, saved drafts, or a "review your details" page users leave open.

Expiry as a first-class outcome. With 2.2.0, expiry is a distinct reason (form_expired) rather than a generic failure. Lean into it:

if ($reason === 'form_expired') {
    return back()->with('status', 'This form has expired — please reload and try again.');
}

Showing the shipped "This form has expired" message is usually right; a generic "something went wrong" just generates support tickets.

Testing. HoneypotService::fake() now resets itself between tests, so you can call it in setUp()/beforeEach without leaking state into unrelated tests. Conversely, if you were relying on a globally-faked honeypot persisting across the suite, move the call into the tests or a per-test hook that runs each time.

Logging while tuning. Turn on HONEYPOT_LOG_VALUE in staging for a week after rollout. It tells you whether rejections are bots or real users tripping autofill. Turn it off in production — it writes user-entered content to logs.

Layering. Keep this alongside rate limiting and, if abuse is targeted, a CAPTCHA on the highest-value endpoints. A honeypot stops drive-by bots cheaply; it is not a defence against a determined attacker.

Gotchas and Tips

  • The one real upgrade hazard: forms with a long minimum fill time. An app configured with a minimum fill time of an hour or more must raise HONEYPOT_MAXIMUM_FILL_SECONDS, or every legitimate submission after the minimum is reached is now rejected as expired. Grep your config and .env for the fill-time keys before deploying.
  • form_expired is a new rejection reason. Any match/switch over reasons, analytics event mapping, or test assertion that expects a fixed set of reasons needs a new arm. Assertions like assertSame('spam', $reason) may now fail on slow tests.
  • Browser tests must select the bait by wire:model. Bait field names are now generated per form (referral_3f9a), so $browser->type('name', ...) or any selector keyed on the old field name is dead. This is the most likely source of red CI.
  • token_min_length is deprecated and ignored. Remove it from your config and delete any test that asserts on it now — it does nothing in 2.2.0 and disappears in 3.0.
  • Don't restyle the hidden field. It now uses screen-reader-only CSS under an inconspicuous class. Overriding it with display:none, hidden, or visibility:hidden makes it invisible to the accessibility tree and to the naive bots the package is targeting, while also risking a11y tooling flags. Leave the shipped styles alone.
  • Expiry is a UX decision, not just anti-spam. HONEYPOT_MAXIMUM_FILL_SECONDS=0 disables it entirely. Use it for anything where a user legitimately returns to a stale tab — otherwise you'll reject real people who left a form open over lunch.
  • Signed tokens don't survive full-page caching. If you cache rendered HTML (CDN, response cache, @cache), the plain-form token goes stale or, worse, is shared across users. Exclude those pages or make the form Livewire-based.
  • Rejections are silent by design — there is no exception to rescue and no failed() hook for this. If you need observability, hook the event/analytics layer rather than wrapping submit() in try/catch; there will be nothing to catch.
  • fake()/real() is the debugging lever. If a form is misbehaving and you suspect the honeypot, call HoneypotService::fake() in a tinker session or a throwaway route to prove the rejection is coming from here before you go hunting elsewhere.
  • Tune after launch, not before. The defaults are sensible; watch the logs in staging, then tighten. Most false positives come from aggressive minimum fill times on the login page, which is the one form real users routinely submit within a second of page load.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log