blendbyte / livewire-honeypot
Honeypot + time-trap spam protection for Livewire 4 forms. No CAPTCHAs, no external requests.
Requires
- php: ^8.5
- laravel/framework: ^13.0
- livewire/livewire: ^4.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.32
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-livewire: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
livewire-honeypot
Spam protection for Livewire and plain Laravel forms, without CAPTCHAs or external requests. Requires PHP 8.5, Laravel 13, and Livewire 4.
- Hidden bait field with a generated name per form, which browsers and password managers leave alone
- Minimum fill time and form expiry, tracked on the server so bots cannot fake them
- Plain forms too:
<x-honeypot />renders a signed token for forms posting to a controller - Silent rejection that answers bots with a fake success
- Optional JavaScript check, events and logging, CSP nonces, and 12 languages
Install
composer require blendbyte/livewire-honeypot
Protect a Livewire form
Add the trait to your component and validate the honeypot before processing the submission:
use Blendbyte\LivewireHoneypot\Traits\HasHoneypot; use Livewire\Component; class ContactForm extends Component { use HasHoneypot; public string $email = ''; public function submit(): void { $this->validateHoneypot(); $this->validate(['email' => 'required|email']); // Process the submission here. $this->reset('email'); $this->resetHoneypot(); } public function render() { return view('livewire.contact-form'); } }
Add the component inside the form:
<form wire:submit="submit"> <x-honeypot /> <label for="email">Email</label> <input id="email" type="email" wire:model="email"> @error('email') <p>{{ $message }}</p> @enderror <button type="submit">Send</button> </form>
Keep the trait on the component, also when you use a Livewire Form object, and call resetHoneypot() after a successful submission. Honeypot errors appear beside the component; style .hp-error to match your form.
Protect a plain form
Outside a Livewire component, the same Blade component renders a signed token for forms posting to a controller:
<form method="POST" action="/contact"> @csrf <x-honeypot /> {{-- Your regular fields and submit button. --}} </form>
use Blendbyte\LivewireHoneypot\Services\HoneypotService; use Illuminate\Http\Request; public function store(Request $request, HoneypotService $honeypot) { $honeypot->validate($request->all()); // Validate and process the rest of the form. }
A rejected submission redirects back with the error shown beside the component. Do not cache pages containing the form, because every render needs a fresh token.
Configuration
The defaults suit most forms: the bait must stay empty, submissions must wait 5 seconds, and forms expire after 1 hour. To change the waiting time or log detections:
HONEYPOT_MINIMUM_FILL_SECONDS=3 HONEYPOT_LOGGING=true
See configuration for every option, per-component settings, and custom bait bindings.
Testing
Bypass the honeypot in tests that focus on the rest of your form:
use Blendbyte\LivewireHoneypot\Services\HoneypotService; beforeEach(fn () => HoneypotService::fake());
Documentation
- Configuration: all settings, per-component overrides, custom bindings, and generated names.
- Plain HTML forms: JavaScript verification and rendering the fields yourself.
- Advanced options: silent rejection, responders, events and logs, CSP, JS verification, views, and translations.
- Testing: testing your forms and running the package's own suites.
- Upgrading: what to check when upgrading to 2.2.
Honeypots catch simple automation, not every bot. Keep normal validation, CSRF protection, and rate limiting on your forms.
Maintained by Blendbyte
Blendbyte builds cloud infrastructure, web apps, and developer tools.
We've been shipping software to production for 20+ years.
This package runs in our own stack, which is why we keep it maintained.
Issues and PRs get read. Good ones get merged.