Start a project
← Insights Laravel

Stopping Form Spam in Laravel

A native, JavaScript-free way to stop most contact-form spam in Laravel using a honeypot field — no reCAPTCHA, and no friction for real users.

By Chris Blackwell · Development

Replace reCAPTCHA with native Laravel abilities.

Almost every Laravel application has some kind of contact form publicly available. Keeping those forms easy to submit is a requirement for your users. The problem is, you’ll be bombarded with spam emails multiple times a day.

Solutions like reCAPTCHA can prevent this altogether, but they come with their own tradeoffs and can be annoying to the user. I want to show you a native Laravel way to stop most spam bots from submitting through your form.

The honeypot

We’re going to use a catch technique called a “honeypot.” That just means we’ll put some code in our app specifically designed to watch for spam bots.

Most spam bots will go to a form and fill out every field available. If there’s a series of checkboxes, the bots are designed to check all of them. We’re going to exploit that by adding a hidden form field that we watch for on submission.

Create a simple contact form. I’ll use Bootstrap for the HTML classes, but the markup doesn’t really matter.

<form action="/contact" method="POST">
  @csrf
  <div class="form-group">
    <label for="name">Full Name</label>
    <input type="text" class="form-control" id="name" name="name" placeholder="Enter your full name" />
  </div>
  <div class="form-group">
    <label for="email">Email address</label>
    <input type="email" class="form-control" id="email" name="email" placeholder="Enter email" />
  </div>
  <div class="form-group">
    <label for="comments">Comments</label>
    <textarea class="form-control" id="comments" rows="3" name="comments"></textarea>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

You’d have this submit to a ContactController with some kind of method on it, like store.

Route::post('contact', 'ContactController@store');

Create the ContactController and add the store method. You’ll want to fire an event that emails you or adds the submission to your ticket system. Whatever your use case, the code will look something like this:

/**
 * Get the contact data and email it to ourselves
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    $formData = $request->validate([
        'fullname' => 'required',
        'email'    => 'required|email',
        'comments' => 'required',
    ]);

    event(new ContactFormSubmitted($formData));

    return redirect()->back()
        ->withSuccess('Your form has been submitted');
}

This works well for humans and your T-1000 series terminators alike — but it’ll send all that form spam flooding your way.

Accept faxes only

The trick is to add a form field that should never be checked. I like to name it “Contact me by fax only.” No human user is ever going to check that.

Add this to your form:

<div class="form-group" style="display: none;">
  <label for="faxonly">Fax Only
    <input type="checkbox" name="faxonly" id="faxonly" />
  </label>
</div>

Make sure not to omit the style="display: none;" attribute — we don’t want this field ever shown to users.

Your next instinct may be to add this field to your validation to make sure it’s blank. While that would work, you’d be telling the bot that its submissions aren’t getting through, which may prompt whoever runs it to tweak the bot to beat your form.

There’s one big rule when defeating spammers and bots: make them think they’re winning. You want the submission to appear to go through and return a successful response.

Add a condition in your controller that checks for the faxonly field:

if ($request->faxonly) {
    return redirect()->back()
        ->withSuccess('Your form has been submitted');
}

If the field is checked, the form just returns a success response without doing anything. Good guys — 1. Spammers — 0.

One last cleanup: we’ve duplicated that success response. Let’s DRY up the code by adding a protected function on the controller that contains the response we’ll always send back.

/**
 * The response to always send back to the frontend
 *
 * @return \Illuminate\Http\Response
 */
protected function formResponse()
{
    return redirect()->back()
        ->withSuccess('Your form has been submitted');
}

Now update store to use it:

public function store(Request $request)
{
    if ($request->faxonly) {
        return $this->formResponse();
    }

    event(new ContactFormSubmitted($request->all()));

    return $this->formResponse();
}

At this point you should be stopping most spam submissions. You can take it further with a bit of JavaScript — my personal favourite is to make the input required and checked by default, then uncheck it with JavaScript after page load (real browsers run the JS; most bots don’t).

I’m sticking with the solution above, since it’s a nice JavaScript-free way to stop the bots.


Building a Laravel app or portal and want the forms, integrations, and anti-abuse handled properly? That’s a big part of what we do — see portals & web apps or start a project.

§ Start

Want this for
your operation?

One reply within a business day. No sales pipeline. We'll either propose a path or point you somewhere better.