DEV Community

Stephen Samra
Stephen Samra

Posted on • Edited on

Prevent spam in Statamic forms

If you're using Statamic's Forms feature to power a public facing form, you'll benefit from using some sort of spam prevention service. I've built plenty of "Contact Us" forms in my day, and I can tell you that eventually it will be targeted with spam messages. It's just a matter of time.

In this article, I'll show you how to add a spam detection service to your Statamic forms and reject any submissions that are deemed to be spam. I'll be using Akismet for this example, but you can use any spam detection service you'd like.

The first step is to create an account with Akismet (or similar service), get your API key, and add it to your .env file:

AKISMET_API_KEY="your-api-key"
Enter fullscreen mode Exit fullscreen mode

...and add a new service to config/services.php so that we can access the API key in our code:

'akismet' => [
    'key' => env('AKISMET_API_KEY'),
],
Enter fullscreen mode Exit fullscreen mode

Next, we'll create a listener that will be triggered when a form is submitted. Statamic dispatches a FormSubmitted event when a form is submitted, but before the submission is saved. The docs say:

You can return false to prevent the submission, but appear to the user as though it succeeded.

This will be perfect for our use case. We can return false in the listener if the submission is deemed to be spam, resulting in the submission not being saved and the site owner not being notified of a new submission.

Create the listener with the following command:

php artisan make:listener HandleFormSubmitted
Enter fullscreen mode Exit fullscreen mode

...and don't forget to register it in app/Providers/EventServiceProvider.php:

<?php

namespace App\Providers;

use Statamic\Events\FormSubmitted;
use App\Listeners\HandleFormSubmitted;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

// use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event to listener mappings for the application.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        FormSubmitted::class => [
            HandleFormSubmitted::class,
        ],
    ];

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Now, we can work on the listener. It's important to note that this code will run when any form is submitted, so we'll need to filter out the form submissions that we don't want to check for spam.

The listener's handle method will receive a FormSubmitted event, which contains the form being submitted and the submission itself. We can use this information to determine if we want to check for spam.

In this example, we'll only be checking the form with the handle of contact_us but if you have multiple forms that you want to check for spam, you could create an array of form handles and check if the submitted form is in that array.

<?php

namespace App\Listeners;

use Statamic\Events\FormSubmitted;

class HandleFormSubmitted
{
    public function handle(FormSubmitted $event): bool
    {
        // Form submissions that aren't for the contact_us form are saved as normal.
        if ($event->submission->form()->handle() !== 'contact_us') {
            return true;
        }

        return ! $this->isSpam($event);
    }

    protected function isSpam(FormSubmitted $event): bool
    {
        // Return true if the submission is spam.
    }
}
Enter fullscreen mode Exit fullscreen mode

You should implement the isSpam method according to the spam detection service you're using. For Akismet, I went with something like this:

use Illuminate\Support\Facades\Http;

protected function isSpam(FormSubmitted $event): bool
{
    $formData = $event->submission->data();

    $response = Http::asForm()
        ->withUserAgent(config('app.name'))
        ->post('https://rest.akismet.com/1.1/comment-check', [
            'api_key' => config('services.akismet.key'),
            'blog' => config('app.url'),
            'user_ip' => request()->ip(),
            'user_agent' => request()->userAgent(),
            'referrer' => request()->headers->get('referer'),
            'comment_type' => 'contact-form',
            'comment_author' => $formData->get('name'),
            'comment_author_email' => $formData->get('email'),
            'comment_content' => $formData->get('message'),
            'comment_date_gmt' => now()->toIso8601String(),
            'blog_lang' => 'en',
            'blog_charset' => 'UTF-8',
            'user_role' => 'guest',
            'is_test' => ! app()->environment('production'),
        ]);

    return $response->body() === 'true';
}
Enter fullscreen mode Exit fullscreen mode

I've removed some logging and error handling for brevity.

That about does it, your Statamic forms should now be protected from spam submissions. This is a good starting point but there's plenty of room for code and feature improvements, such as:

  • Showing spam submissions in the Statamic Control Panel
  • Submitting false positives/ham from the Statamic Control Panel
  • Being able to swap out the spam detection service for a different one
  • etc.

Maybe I'll write a follow up article on implementing some of these features, please let me know if that's something you'd be interested in.

I hope you found this article useful. If you have any questions or feedback, let's chat in the comments below. Or, you can always reach me on Bluesky or Twitter.

Top comments (0)