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

darvis/livewire-honeypot

darvis/livewire-honeypot provides lightweight honeypot spam protection for Livewire forms, blocking bots without CAPTCHAs or friction for real users. Simple to install and configure, it integrates cleanly with Laravel and existing Livewire components. No JavaScript required. Drop-in protection.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer:

composer require arviddejong/livewire-honeypot

Start with the package documentation at https://arviddejong.github.io/livewire-honeypot/, then read the v1.6.0 upgrade notes if you already use the package. The first practical use case is protecting a public Livewire form: render the honeypot component inside the form, add the HasHoneypot trait to the Livewire component, and call validateHoneypot() before processing the submission.

Minimal Livewire setup:

<form wire:submit="submit">
    <x-honeypot />

    <input type="text" wire:model="name" />
    <input type="email" wire:model="email" />
    <textarea wire:model="message"></textarea>

    <button type="submit">Send</button>
</form>
use Livewire\Component;
use ArvidDeJong\LivewireHoneypot\Traits\HasHoneypot;

class ContactForm extends Component
{
    use HasHoneypot;

    public string $name = '';
    public string $email = '';
    public string $message = '';

    public function submit(): void
    {
        $this->validateHoneypot();

        $this->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email'],
            'message' => ['required', 'string'],
        ]);

        // Send the message.
    }
}

For a plain Laravel form, use the package's HoneypotService::validate($request->all()) flow as documented. As of v1.6.0 this works correctly in a standard Laravel application where ConvertEmptyStringsToNull is active.

Implementation Patterns

Default Livewire form

Use the default pattern unless you have a reason to customize it:

  • Put <x-honeypot /> inside the <form>.
  • Put use HasHoneypot; on the Livewire component, not on a form object.
  • Call $this->validateHoneypot() at the start of the submit action.

This keeps the default field name, hp_website, and default error key aligned automatically.

Custom Livewire binding

If you bind the honeypot to a different Livewire property, the component and the validation call must use the same path:

<x-honeypot wire:model="contact.hp_website" error-key="contact.hp_website" />
public array $contact = [
    'hp_website' => '',
];

public function submit(): void
{
    $this->validateHoneypot(
        model: 'contact.hp_website',
        errorKey: 'contact.hp_website',
    );

    // ...
}

Initialize the bound property as an empty string. Display the error under the custom key if needed:

@error('contact.hp_website')
    <span>{{ $message }}</span>
@enderror

Plain Laravel forms

Validate the full request payload, not a filtered subset:

public function store(Request $request, HoneypotService $honeypot)
{
    $honeypot->validate($request->all());

    $data = $request->validate([
        'name' => ['required', 'string'],
        'email' => ['required', 'email'],
        'message' => ['required', 'string'],
    ]);

    // ...
}

Passing $request->validated() can be a problem because hp_website is usually not part of your validation rules and may be excluded from the validated array. The package expects the documented full payload shape.

Livewire form objects

Keep the HasHoneypot trait and the validateHoneypot() call on the Livewire component, not on a form object or another non-component class. In v1.6.0, using the trait on a form object throws a LogicException with guidance to move it to the component. This is better than the previous silent behavior where the form object had no honeypot start time and every submission was rejected as spam.

Testing

Test the real middleware stack when you want confidence that empty-string/null handling works. Do not default to withoutMiddleware() for the plain-form test path; that bypasses ConvertEmptyStringsToNull and can hide the exact class of bug fixed in v1.6.0.

Useful cases to cover:

  • A submission with hp_website present but null/empty passes.
  • A submission with hp_website absent is rejected.
  • A submission with hp_website filled is rejected.
  • A custom wire:model path is rejected when the bound property is filled.

Overriding validation behavior

A component that defines its own validateHoneypot() method keeps working. Use this extension point if you need custom messaging or logging, but preserve the package's core distinction: empty means human, filled means spam, absent means invalid.

Gotchas and Tips

Null is not absent

v1.6.0 fixes the important distinction between a present-but-null hidden field and a missing hidden field:

  • Present and null or empty: treated as a normal empty honeypot.
  • Absent: still rejected.
  • Filled: still spam.

This matters because Laravel's ConvertEmptyStringsToNull middleware turns the empty hidden input into null in a default application. Before v1.6.0, that made every plain-form submission look like the field was missing.

Remove the old null workaround

If you added this workaround from the 1.5.1 docs:

array_map(fn ($value) => $value ?? '', $request->all())

you can remove it after upgrading. Leaving it in is harmless, but it is no longer needed.

wire:model mismatches are a security issue

Before v1.6.0, validateHoneypot() always read hp_website. If a view used:

<x-honeypot wire:model="contact.hp_website" />

but the component still called $this->validateHoneypot() with defaults, the bound field was not actually checked. Search your views for <x-honeypot wire:model= and make sure each custom binding has a matching call:

$this->validateHoneypot(
    model: 'contact.hp_website',
    errorKey: 'contact.hp_website',
);

Forms using <x-honeypot /> with no attributes need no change.

Keep the trait on the component

If you see the new LogicException, move both the trait and the validateHoneypot() call to the Livewire component. This is most likely to affect applications that put the trait on a Livewire form object. Livewire does not run a mount hook on those objects, so the honeypot never gets a valid start time and every submission is rejected.

Do not conditionally render the honeypot

An absent hidden field is still rejected after v1.6.0. Avoid wrapping <x-honeypot /> in @if, and do not omit it from forms that call validateHoneypot().

Watch for browser autofill false positives

Honeypot fields can be filled by aggressive browser autofill or password managers. If real users report "Spam detected.", inspect the submitted hp_website value before changing validation logic. Keep the field hidden from users and assistive technology according to the package docs.

Debugging "Spam detected."

Work through the likely causes in this order:

  1. Is <x-honeypot /> actually rendered inside the submitted form?
  2. Is the hidden field present in the payload?
  3. Is the submitted value genuinely empty?
  4. For a custom wire:model, does validateHoneypot() receive the same model and error key?
  5. Does the bound property start as an empty string?
  6. Is the trait on the Livewire component rather than a form object?
  7. Are you passing the full request payload to the plain-form service?

Upgrade posture

v1.6.0 is mostly a bug-fix and security release. Default Livewire forms and default plain-form usage should require no changes beyond removing the optional workaround. Applications using custom wire:model bindings should treat the matching validateHoneypot(model: ..., errorKey: ...) call as required, and applications using the trait on form objects should expect the new LogicException until the trait is moved to the component.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings