spatie/laravel-honeypot
Protect Laravel forms from spam using a honeypot field plus a minimum-time check. Add the Blade component (or pass values manually for Inertia) and suspicious submissions are automatically discarded.
Installation:
composer require spatie/laravel-honeypot
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\Honeypot\HoneypotServiceProvider" --tag="honeypot-config"
Add Honeypot to a Form: In your Blade template, include the honeypot component:
<form method="POST" action="{{ route('contact.submit') }}">
<x-honeypot />
<!-- Your form fields -->
<input name="email" type="text">
<button type="submit">Submit</button>
</form>
Protect the Route: Apply the middleware to your form submission route:
Route::post('/contact', [ContactController::class, 'store'])
->middleware(\Spatie\Honeypot\ProtectAgainstSpam::class);
Scenario: Protect a contact form from spam submissions.
<x-honeypot /> to your form.Component/Directive: Use <x-honeypot /> or @honeypot in Blade templates. Both inject two hidden fields:
my_name (configurable): Should always be empty for valid submissions.valid_from: Encrypted timestamp to detect rapid submissions (default: 1 second threshold).Dynamic Forms: For dynamic forms (e.g., AJAX), manually include the fields:
form.append('my_name', '');
form.append('valid_from', '{{ $honeypot->encryptedValidFrom }}');
Route-Level Protection:
Route::post('/contact', [ContactController::class, 'store'])
->middleware(\Spatie\Honeypot\ProtectAgainstSpam::class);
Global Middleware:
Add to app/Http/Kernel.php:
protected $middleware = [
// ...
\Spatie\Honeypot\ProtectAgainstSpam::class,
];
honeypot_fields_required_for_all_forms: false in config.Middleware Groups: Protect specific groups (e.g., auth routes):
Route::middleware(['web', \Spatie\Honeypot\ProtectAgainstSpam::class])->group(function () {
Auth::routes();
});
Share Honeypot Data:
In app/Http/Middleware/HandleInertiaRequests.php:
public function share(Request $request): array {
return array_merge(parent::share($request), [
'honeypot' => new \Spatie\Honeypot\Honeypot(config('honeypot')),
]);
}
Render in Vue:
<template>
<div v-if="honeypot.enabled" style="display: none;">
<input type="text" v-model="form[honeypot.nameFieldName]" :name="honeypot.nameFieldName">
<input type="text" v-model="form[honeypot.validFromFieldName]" :name="honeypot.validFromFieldName" :value="honeypot.encryptedValidFrom">
</div>
</template>
<script setup>
const form = useForm({
email: '',
[defineProps(['honeypot']).honeypot.nameFieldName]: '',
[defineProps(['honeypot']).honeypot.validFromFieldName]: defineProps(['honeypot']).honeypot.encryptedValidFrom,
});
</script>
Add Trait and Property:
use Spatie\Honeypot\Http\Livewire\Concerns\{UsesSpamProtection, HoneypotData};
class ContactForm extends Component {
use UsesSpamProtection;
public HoneypotData $extraFields;
public function mount() {
$this->extraFields = new HoneypotData();
}
public function submit() {
$this->protectAgainstSpam(); // Throws exception if spam
// Process form...
}
}
Blade Template:
<form wire:submit="submit">
<x-honeypot livewire-model="extraFields" />
<!-- Form fields -->
</form>
Extend the default BlankPageResponder:
use Spatie\Honeypot\SpamResponder\SpamResponder;
class CustomResponder implements SpamResponder {
public function respond(): void {
abort(403, 'Spam detected. Please try again.');
}
}
Update config:
'respond_to_spam_with' => \App\SpamResponders\CustomResponder::class,
Missing Honeypot Fields:
<x-honeypot /> or @honeypot. Otherwise, legitimate submissions may fail.'honeypot_fields_required_for_all_forms': false in config or use route-level middleware.CSRF Token Conflicts:
my_name, valid_from) must not conflict with existing form fields (e.g., name, valid_from).'name_field_name' => 'honeypot_name',
'valid_from_field_name' => 'honeypot_timestamp',
Livewire Property Binding:
livewire-model in <x-honeypot /> will cause the honeypot data to not sync with Livewire.livewire-model="extraFields" (or your property name).Inertia Form Submission:
form[honeypot.nameFieldName]: '',
form[honeypot.validFromFieldName]: honeypot.encryptedValidFrom,
Time Zone Issues:
valid_from timestamp uses the server's time zone. If your app uses a non-default time zone, submissions from users in other time zones might be flagged.config('app.timezone') matches your expectations.CSP (Content Security Policy) Conflicts:
'with_csp': true in config and install laravel-csp.Log Spam Attempts:
Extend the SpamProtection class to log blocked requests:
use Spatie\Honeypot\SpamProtection;
use Illuminate\Support\Facades\Log;
class CustomSpamProtection extends SpamProtection {
protected function checkForSpam(): void {
try {
parent::checkForSpam();
} catch (\Spatie\Honeypot\Exceptions\SpamException $e) {
Log::warning('Spam attempt blocked', [
'ip' => request()->ip(),
'user_agent' => request()->userAgent(),
'honeypot_field' => request()->honeypot_name,
]);
throw $e;
}
}
}
Update config:
'spam_protection' => \App\SpamProtection\CustomSpamProtection::class,
Test with Real Bots: Use tools like Browserling Form Tester to simulate bot submissions. Fill the honeypot field manually to verify detection.
Disable for Testing: Temporarily disable honeypot in config:
'enabled' => env('HONEYPOT_ENABLED', false),
Or override in .env:
HONEYPOT_ENABLED=false
SpamProtection class to add customHow can I help you explore Laravel packages today?