Stop Spam in Laravel Forms: Honeypot, Rate Limits, and Smarter Defaults | FilaForms                                 [ ![Filaforms Logo](https://filaforms.app/logo.svg)FilaForms

 ](https://filaforms.app)  [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app#pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app)  [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app#pricing)

 [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app#pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app) [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app#pricing)

   ![FilaForms](https://filaforms.app/logo.svg) FilaForms

 TutorialsStop Spam in Laravel Forms: Honeypot, Rate Limits, and Smarter Defaults
=======================================================================

 filaforms.app/blog

  [    Back to blog ](https://filaforms.app/blog) [ Tutorials ](https://filaforms.app/blog/category/tutorials)

Stop Spam in Laravel Forms: Honeypot, Rate Limits, and Smarter Defaults
=======================================================================

 Manuk Minasyan ·  May 22, 2026  · 8 min read

 You ship a contact form on Monday. By Wednesday morning you open the submissions table and the first twenty rows are crypto pitches, Cyrillic gibberish, and links to sites you really don't want to click. One of them has somehow filled the "phone number" field with a Telegram username.

I've seen this happen to every public Laravel form I've ever shipped, including FilaForms' own. The moment a form is reachable without auth, it gets scraped, fingerprinted, and added to some bot's nightly run. It doesn't matter if your site has ten visitors a day. Spam bots don't care about your traffic.

The default instinct is to slap reCAPTCHA on it and move on. Don't. Or at least, don't lead with it. reCAPTCHA solves the bot problem at the cost of a real chunk of your conversions, and there are four cheaper, less annoying things you should do first. CAPTCHA is the last layer, not the first.

Here's the layered approach to Laravel form spam protection we ship with FilaForms by default, plus the layers we leave to you because not every form needs them.

Why we don't lead with CAPTCHA
------------------------------

Every CAPTCHA costs you submissions. reCAPTCHA v2 ("click the traffic lights") visibly annoys people. reCAPTCHA v3 runs invisibly but tags real users as bots often enough that you'll get complaints. I've watched a form's completion rate drop 5 to 10 percent the day a CAPTCHA went on, and I've watched it climb back up the day we took it off.

There's also a UX tax you don't see in your analytics. Screen reader users hit the audio fallback and curse you. Mobile users on a flaky connection wait for the widget to load and bail. EU users on privacy-hardened browsers get the harder challenges because their fingerprint looks weird to Google. And Google getting a free signal off your form is its own conversation.

The honest framing is this: a CAPTCHA is a tax on every legitimate user to catch the small percentage of bots that get past the other layers. So put the cheap, invisible layers in first, measure how much spam actually gets through, and only reach for CAPTCHA if you still need it.

Layer 1: Honeypot
-----------------

A honeypot is a form field that bots will fill and humans won't. You add a field to the form, hide it from real users with CSS or `aria-hidden`, and on the server side you reject any submission where it's not empty.

Bots fill every input they see in the DOM. That's their job. They don't run your CSS, they don't care that the field is off-screen, they don't read your `aria-hidden`. They just enumerate inputs and fill them.

In FilaForms you add this like any other field — name it something a bot would want to fill, like `website` or `phone_2`, then hide it from the rendered form with conditional visibility. Then listen for the `FormSubmitted` event and bail if the trap field is non-empty:

```
namespace App\Listeners;

use FilaForms\Core\Events\FormSubmitted;

class RejectHoneypotSpam
{
    public function handle(FormSubmitted $event): void
    {
        $trap = $event->submission->data['website'] ?? null;

        if (filled($trap)) {
            $event->submission->delete();
        }
    }
}

```

This single layer kills the vast majority of low-effort spam — the bots running on someone's residential proxy, hammering thousands of forms a night. They're not smart. They fill the trap. You drop the row. Done.

Layer 2: Time-to-submit floor
-----------------------------

Bots fill a form in under two seconds. A human filling out a contact form takes at least fifteen, and a multi-field form takes a minute or more. That gap is one of the cleanest signals you have.

FilaForms already records `FormEvent` rows for each session — a `START` when the form opens, a `SUBMIT` when it's sent. The gap between those two timestamps is your spam filter. If it's under a sensible floor — I'd start with three seconds for a single-field form, ten seconds for anything longer — drop it.

In a listener:

```
public function handle(FormSubmitted $event): void
{
    $start = FormEvent::query()
        ->where('form_id', $event->form->id)
        ->where('session_fingerprint', $event->submission->session_fingerprint)
        ->where('event', FormEventType::START)
        ->latest('created_at')
        ->value('created_at');

    if ($start && $start->diffInSeconds(now()) submission->delete();
    }
}

```

The trap here is being too aggressive. A returning user with autofill can submit a one-field newsletter form in two seconds without being a bot. Tune the floor to your actual form, and check your analytics for the median completion time before you pick a number. Set the floor at maybe a quarter of that median.

Layer 3: Rate limiting per session
----------------------------------

Laravel ships with [`RateLimiter::for(...)`](https://laravel.com/docs/12.x/routing#defining-rate-limiters), and the `throttle` middleware works on any route. For a public form endpoint, you almost always want this on.

The catch is what you key on. If you key on IP, you'll false-positive office networks, coworking spaces, and anywhere with NAT — meaning a real customer can't submit because their colleague submitted ten minutes ago. For forms, I prefer keying on session ID for normal use, and falling back to IP only when there's no session.

```
RateLimiter::for('public-forms', function (Request $request) {
    return $request->session()->isStarted()
        ? Limit::perMinute(5)->by($request->session()->getId())
        : Limit::perMinute(3)->by($request->ip());
});

```

Then `->middleware('throttle:public-forms')` on the form submission route. Five submissions per minute per session is generous for humans and brutal for anything trying to brute-force. The session-fingerprinted analytics in FilaForms will also surface anomalies — fifty submissions in five minutes from one session is a useful thing to be able to see when something slips through.

Layer 4: Content fingerprints
-----------------------------

Some spam will get past the first three layers. Determined bots run headless browsers, fill forms in human-like time, and have unique sessions. You catch those by looking at what they wrote.

A `FormSubmitted` listener that does cheap heuristic checks before saving is enough for most cases. Block submissions where the message field is overwhelmingly Cyrillic on an English-only form. Block submissions where the same text body has appeared three times in the last hour. Block message bodies that are 90 percent URL.

These rules don't need to be smart. They need to be cheap. A regex for `[\p{Cyrillic}]` on a field that should only contain English, a `Cache::remember` of submission hashes for the last 60 minutes — that's it. You can layer in more sophisticated content scoring later if you actually need it, but you usually don't.

Be careful with false positives here. Real users send weird messages. Log what you block before you delete it, at least for the first week, so you can sanity-check what's getting caught.

Layer 5: CAPTCHA, only if you need it
-------------------------------------

If you've shipped all four layers and you're still getting spam through, now is when CAPTCHA earns its keep. Pick one and add it to the public form route.

My preference is [Cloudflare Turnstile](https://www.cloudflare.com/application-services/products/turnstile/). It's free, it's invisible most of the time, it doesn't feed Google, and the UX is better than reCAPTCHA. hCaptcha is the EU-friendly alternative if you're already in the Cloudflare-skeptical camp, or if a customer specifically asks for it. Skip reCAPTCHA v3 unless you have a strong reason — the tracking and the false positives aren't worth it in 2026.

What we ship by default
-----------------------

FilaForms turns on the honeypot and the time-to-submit floor by default. They're invisible to legitimate users, they don't need any configuration, and they catch the majority of automated spam without a single legitimate submission lost.

Rate limiting, content fingerprints, and CAPTCHA are opt-in. The reasoning is simple: every public form needs the first two, but the rest depend on your form, your audience, and your risk tolerance. A newsletter signup needs different defaults than a job application or a healthcare intake form. We don't want to make assumptions for you on the layers where the tradeoffs actually matter.

The point
---------

Layered defense beats one heavy gate. Five cheap checks that each catch a different kind of bot will outperform one expensive CAPTCHA that catches everything but also annoys 5 percent of your real users into bouncing. Start with the invisible layers, measure what gets through, and only add friction when the data says you need to.

If you're routing submissions out to third-party services, the same listener pattern works for filtering before the data leaves your app — see [how FilaForms wires submissions to webhooks](/blog/webhooks-in-filaforms-send-submissions-anywhere) for the delivery side. And if you're building [a tighter lead-gen form](/blog/lead-generation-forms-in-laravel-a-template-that-converts) that captures business email and qualifies the buyer, the layers in this post are the baseline you want underneath it before you start optimizing for conversion.

The [FilaForms plugin](https://filaforms.app) ships with the first two layers on by default, so a fresh install already has a baseline. The first time you wake up to a clean submissions table is a good morning. Worth a few hours of setup.

 Related posts
-------------

 [  Tutorials   Apr 30, 2026

 File Uploads in Filament Forms: Storage, Validation, and Security
-------------------------------------------------------------------

File uploads are where most form builders cut corners. They'll let you add a file field and call it done, leaving you to figure out storage and validation

 ](https://filaforms.app/blog/file-uploads-in-filament-forms-storage-validation-and-security) [  Tutorials   Mar 30, 2025

 Conditional logic in Laravel forms: when fields should show (and hide)
------------------------------------------------------------------------

How to build forms where fields appear based on previous answers. Covers Filament's reactive forms, Livewire approaches, and common patterns.

 ](https://filaforms.app/blog/conditional-logic-in-laravel-forms-when-fields-should-show-and-hide) [  Tutorials   Mar 27, 2025

 Multi-step forms in Filament: a complete guide
------------------------------------------------

How to build multi-step forms in Laravel with Filament — wizard components, progress tracking, conditional steps, and where most implementations go wrong.

 ](https://filaforms.app/blog/multi-step-forms-in-filament-a-complete-guide)

    ![FilaForms Logo](/logo.svg) FilaForms

 Laravel form infrastructure for Filament. Stop rebuilding forms on every project.

 ### Product

 [ Features ](https://filaforms.app#features) [ Documentation ](https://docs.filaforms.app) [ Blog ](https://filaforms.app/blog) [ Pricing ](https://filaforms.app#pricing) [ Contact ](mailto:hello@filaforms.app)

 ### Legal

 [ Terms of Service ](https://filaforms.app/terms-of-service) [ Privacy Policy ](https://filaforms.app/privacy-policy)

  © 2025-2026 FilaForms. All rights reserved.

 [    ](mailto:hello@filaforms.app) [    ](https://x.com/MinasyanManuk)
