Ultimate guide to preventing WordPress contact form spam emails (without using captchas)

How to prevent spam contact form submission in WordPress without captchas using PHP and JavaScript code

Protect your website from spam emails and bots with proven prevention methods

If there’s one thing we can all agree on, it’s that we hate contact form spam.

And like all security challenges, it’s a constant ever-changing battle between the spammers and website owners.

I myself have been battling this one for years, across WPForms, GravityForms and more.

And it would make it a heck of lot easier if the form plugin developers stopped arbitrarily removing actions hooks and filters.

I’m looking at you WPForms!

So I’ve just spent the last few days:

  • Re-implementing my spam filtering for WPForms
  • Setting up a Bricks Builder form and figuring out validation
  • Tyring out Fluent Forms, and adding spam filtering to it

and now I’m ready to share what I’ve learned in this complete* guide to preventing contact form spam on WordPress, regardless of which contact form plugin you’re using, without pissing off your legitimate leads and customers.

I’m going to cover:

  • Using the form plugin’s built-in settings
  • Using the comment blacklist to filter out spammy terms
  • Other things to use as flags to catch out junk before it gets to you
  • Some spam-adjacent strategies

And of course, this will involve a fair amount of PHP.

* Includes everything except techniques that obstruct people.

But first, some important caveats.

1. No captchas or user-obstruction

If you look at many of the top articles on form spam prevention, they all sing the praises of captchas.

And they may very well work, but I categorically hate them.

“Wait. Before you can reach out to me, you must first prove that you are human”.

Ugh.

I also detest forcing real people to verify their email address, or adding a password to your form (like WTF?) or forcing people to login before they can submit a form.

I’d also put blocking copy and paste on the page or website in the same category, because it just makes it harder for real people to use your site.

I hate anything that penalises legitimate human beings, in an effort to stop the bad players.

As far I’m concerned, that’s bad form.

Basically it treats everyone as guilty until proven innocent and that gets a big “hell no” from me.

Besides, if you desperately want to use captchas, there’s a ton of articles out there talking you through it, so go read one of those.

2. No perfect solutions

If you came here looking for a foolproof solution that works 100% of the time and doesn’t require maintenance, then make like a tree and leave.

Nobody can promise that, and if they are, then they’re probably selling something.

My approach, like any solution, is going to require monitoring, maintenance, and the occasional complete overhaul.

Instead, what I’m aiming here is to show you the different ways you’ll need to tackle this, to reduce your junk form submissions to an acceptably low level.

Of course, if that all sounds too hard, you might like to look into a paid service that promises to take care of all of this for you, like CleanTalk.

Heck, you might even be able to find a not-too-bright AI tool to figure it out for you. (That gives me an idea…)

3. No time wasting

While I am willing to take this matter into my own hands, I also don’t want it to take any longer than absolutely necessary to maintain.

At the moment, I spend about 15 minutes a week updating my spam terms list, after collecting any spam submissions I do receive over the course of the week.

No manually adding keywords to individual forms (looking at you again, WPForms!), no manually capturing and adding spammer email addresses or IP addresses.

And no doing things that are kind of pointless, like blocking particular countries, because VPNs make those things irrelevant.

Blergh!

You’ll see what I mean later about reducing or even eliminating fiddle, fuss and faffing in my approach.

4. No spoon feeding

I’m also not going to give you one big block of code that you can post on your site, wash your hands of the matter, and then forget about it.

I’m going to give you principles, and a laundry list of all of the things you need to consider.

I’m going to make you think about your specific situation, and figure out how to create the right solution for you.

Besides, rolling your own solution instead of 100% relying on someone else means you’ll do things a little differently from everyone else.

After all, you don’t have to beat the tiger. You just have to run faster than the other guy.

I’ll also throw in some code snippets, and even some examples and tips for specific form plugins, including:

So, with that all straightened out, let’s get onto the solution.

You might want to grab a cuppa or another can of your favourite energy drink…

Strategies to stop WordPress contact form spam

1. Make use of your form plugin’s security measures

The first and simplest strategy to reducing form spam submissions is to make full use of your form plugin’s security measures.

Each plugin comes with its own set of features, so take the time now to explore them fully, and switch on anything you can.

A. Field validation

The most obvious one you need to have on your form is input validation.

Most form plugins include this by default, but just make sure it’s turned on and configured with sensible settings.

These validations will do things like:

  • Preventing blank fields (by making fields required)
  • Make sure email addresses are valid addresses
  • Making sure inputs don’t violate their type e.g. letters in a phone number field

B. Spam protection

Some plugins simply offer a “spam protection” setting that works behind the scenes to block suspicious submissions.

Although we’re might not be told how they work, and how well they work, it doesn’t hurt to turn them on.

These might include things like:

  • Honey pots – Invisible fields that only bots fill out
  • Basic filtering rules – Usually stopping only the most obvious and egregious spam
  • Identifying bot-like behaviour
Examples
  • WPForms – “Modern” spam protection
  • Fluent Forms – Blocking empty submissions, honeypot

C. Custom rules

Some plugins also offer the option to add custom rules that you can use to validate submissions.

You can enter specific conditions that an entry must or must not meet in order to succeed.

These are nice but can be a little labour-intensive to maintain, especially if they’re implemented at the form level.

Examples
  • Fluent Forms – Advanced Form Validation, created on a per-form basis

D. Keep them up to date

And of course, make sure to keep your form plugin up to date.

As they discover new vulnerabilities or ways to reduce spam, they’ll add them to their plugin, so you want to make sure you’re taking advantage of these updates as they’re released.

2. Spam-adjacent strategies

In addition to making your form plugin work as hard as possible for you, there are some strategies that are close-by, that can strengthen your efforts, without too much extra work.

A. Firewall plugin

Installing and setting up a good firewall plugin will help reduce spam submissions, because they can detect certain kinds of requests that only come from bots.

Not all spam submissions are done by bots, but if we can at least block a large percentage of them with a firewall, it’s definitely worth doing.

And as a bonus, this will harden your site generally against hackers and other security risks.

Again, there are good firewall plugins, and bad ones.

Make sure you choose one that:

  • Has a decent number of installs (more likely to stick around)
  • Has a pretty high star rating (I prefer 4.7 or higher)
  • Has very few 1-star ratings
    • Some plugins have lots of 5’s, some fours, a few threes and so on. Others have lots of 5’s and lots of 1’s. I avoid the latter.
  • Is active on their support forum
  • Does not slow down your site
    • Read their blurbs, looking for “fast”, “lightweight” or “will not slow down your site”. You may also need to test performance (page load speed) with and without the plugin.

And then make sure to configure the heck out of it. (But not so much that you lock yourself out of your own site!)

B. Anti-spam plugins

There’s a range of anti-spam plugins on offer for WordPress.

They come with mixed reviews, and may impact site performance, so be careful before installing just any one of them.

In addition, some of them only add features that your form plugin may already have, so beware of doubling up here.

Or, they may not even filter form submissions, so double check thoroughly before adding one of them and expecting it to magically fix everything.

They may also help block spam in comments, registration pages, product reviews and more.

Some of the more common ones include:

  • CleanTalk – 4.8 stars (3021 reviews, 200K installs)
  • Anti-Spam Bee – 4.8 stars (219 reviews, 700K installs)
  • Akismet – 4.7 stars (1109 reviews, 6M installs [only this high because it’s installed by default when you first set up WordPress and most people don’t delete it])
  • Titan Anti-spam & Security – 4.5 stars (366 reviews, 80K installs)
  • Zero Spam for WordPress – 4.2 stars (136 reviews, 30K installs)

I’ve heard some good things about CleanTalk and given its relatively low annual cost, I might even test it out on one of my sites.

I’ve also heard less than great things about Turnstile from CloudFlare. Honestly, I generally give CloudFlare a wide berth because I don’t like the way they do things.

C. Blocking specific languages and/or characters

Even though this is something you’d have to implement via your form plugin, either through custom rules, or via PHP, I consider it to be spam-adjacent, because it’s not really tackling the spam problem head on.

Sure I might not be able to read Indonesian or Cyrillic, but does that mean I should blanket ban those languages entirely?

I’m undecided about this one. I’m not a fan of blanket policies, because again, they can end up penalising real human beings.

However, I have seen spammers use characters out of non-English alphabets to replace English letters in spammy terms, to avoid detection.

And I have no issue blocking those specific characters or corrupted words.

3. Block common spammer tricks

Spam submissions follow some reasonably predictable patterns.

Here’s some of the most common ones I’ve seen, but learn from the spam you do receive, and come up with your own unique patterns that you can recognise.

I’ll show you how to prevent these bad behaviours using code further down below.

A. URLs

Spammers often put URLs into their submissions, often in the weirdest places.

You could choose to block URLs entirely in your submissions, or you can block them in name fields, or only allow them in text areas.

You can block URLs simply by adding http to any filtering code or validation rule.

I’ve also seen them use URL shortening services quite consistently, but making a big list of those seems like a right pain in the a$$.

B. Name fields

I’ve seen spammers put entire URLs into name fields to bypass spam filtering, but happily most genuine names are pretty simple when it comes to characters.

You might get hyphens/dashes, full-stops and apostrophes.

You’re certainly unlikely to get exclamation marks, dollar signs, question marks, hashes or even commas.

So you could safely block any submission with those characters in the name field.

If your form plugin allows you to add this via the interface as a custom rule, great. Otherwise, you’ll need to use custom PHP.

C. Input length

Again, most forms have “typical” inputs that normal users would enter, and you can use these to place sensible limits on what is allowed in your forms.

For example you might say:

  • Names must be at least two characters
  • Names can’t be more than 200 characters
  • Email addresses can’t be more than 200 characters
  • Messages must be at least 20 characters

Many forms allow you to set a maximum length for any input field, so I strongly recommend you set this on all of your fields.

Just be generous so you don’t piss off a genuine human who happens to have a longer than normal name, or wants to type a really detailed message.

And although most forms allow you to set “required” fields, meaning that they can’t be empty, they don’t always allow you to set a minimum length, so I have a Javascript solution for you below.

D. Duplicates

I’ve also seen spammers put the same value for name and email, or email and message, or name and message.

And while that’s technically valid, in the world of real human beings, that’s complete nonsense. No-one would ever fill out a normal contact or email newsletter signup form that way.

So you can also check for duplicated field values and use that to reject spam submissions.

4. Use the comment blacklist

This is my favourite way to block spam submissions.

Every week, I go through the submissions that slipped through, and pick out key terms and phrases like “bitcoin” or “business loan” and add them to my list.

My current list is almost over 1200 lines long, and I’ve been collecting it for over three years.

And no, I’m not going to share it with you because:

  1. Then I’d also be sharing it with the spammers, and
  2. What is spam for me, may not be spam for you (and vice versa) because you know your business best

But I basically compare all of the submitted form field values to the values on this list, and if a spam term is detected, the submission is rejected.

If you’re wondering where your comment blacklist is, it’s under Settings > Discussion > Disallowed Comment Keys in your WordPress back end.

Just add any terms you want flagged and save your settings.

Using PHP and Javascript code to block spam form submissions

There’s two main parts to writing code to block spam submissions:

  1. Creating rules to identify violations in field inputs
  2. Identifying your form plugins’ action hooks and filters so you can filter submissions on the way past

Any of the code you write will need to be added to your site, via either:

  • Your theme’s functions.php file
  • A code snippet plugin such as Code Snippets or WPCode
  • Your own custom-rolled plugin

Before you proceed, be warned:

You can break your entire site with custom code.

All it takes it one character out of place, and you’re up sh!t creek without a paddle.

So make sure you have an emergency way back in to your site (like FTP or web host file manager access) just in case you make a mistake.

(And you will, I promise!)

1. Rules and code to identify spam submissions

Keep in mind that these are just examples. They most likely won’t work as is for your needs, because you’ll need to:

  • wrap them in a function
  • attach that function to the appropriate hook
  • figure out what format your form values are passed to the hook

So use these as a starting point, to teach you the general principles. Not provide you a finished solution on a plate.

You’re going to have to use your brain here, sorry!

A. Checking for blacklisted terms

This code takes a value (a form field input) and runs it past all of the terms in your comment blacklist.

function nhs_value_contains_blacklisted_term( $value ){

    $value_is_spammy = false;

    $mod_keys = trim( get_option( 'blacklist_keys' ) );
    if ( '' == $mod_keys ) {
    }
    else{
        $words = explode( "\n", $mod_keys );

        // Step through spam terms in turn
        foreach ( (array) $words as $word ) {
            $word = trim( $word );

            // Skip empty lines
            if ( empty( $word ) ) {
            continue; }

            // Escape terms so that '#' chars in the spam words don't break things
            $word = preg_quote( $word, '#' );

            // Match form fields to spam terms
            $pattern = "#$word#i";
            if ( preg_match( $pattern, $value) ) {
                $value_is_spammy = true;
            }
            
        }

    }

    return $value_is_spammy;
}

B. Identifying blacklisted submissions

This code runs through the fields of the form input, and checks whether any of them fails the blacklist test.

This is generic code, that requires that you have your form inputs in an array of key value pairs, called $form_fields.

How your form does this will depend on which plugin you’re using.

// Check entry for spam terms
$contains_spam = false;

foreach ( $form_fields as $key => $value ){
    if( nhs_value_contains_blacklisted_term( $value ) == true )
        $contains_spam = true;
}

This code only works if you have access to an action hook or filter that handles the entire submission. If you only have access to field-level hooks, you won’t be able to run this code this way.

Instead you’ll need to run this check at the field level, like so.

// Check that value doesn't contain a spam term
$contains_spam = false;

if( nhs_value_contains_blacklisted_term( $field_value ) == true
    $contains_spam = true;

C. Identifying banned characters in name inputs (or any other types)

This code is for fields containing “name” type inputs, but can be adapted for any field type and any array of chosen banned characters or strings.

This code assumes you have the type of field in a variable called $field_name and the field value in a variable called $field_value.

Of course, if you have a form that doesn’t tell you the type of field, or you can’t identify name fields, you’ll need to be careful with applying this code.

Notes

  • The return value here is only for hooks that allow you to add error messages to your form. Again, this depends on the form plugin you’re using.
  • Only set the value of $field_has_issue to false ONCE, at the very start of your function that does all of these checks
// Validate name text fields
$field_has_issue = false;
if( str_contains( $field_name, 'name' ) ){

    // Check if name contains banned characters
    $banned_name_strings = array( "!", "$", "?", "#", "http");

    foreach ( $banned_name_strings as $banned_string ){
        // Clean up string
        $banned_string = trim( $banned_string );
        // Skip empty lines
        if ( empty( $banned_string ) ) {
            continue; }
        // Escape terms so that '#' chars in the spam words don't break things
        $banned_string = preg_quote( $banned_string, '#' );

        $pattern = "#$banned_string#i";
        if ( preg_match( $pattern, $field_value ) ) {
            $field_has_issue = true;
        }
    }
    
    // Check that name is long enough
    if( strlen ( $field_value ) <= 2 ){
        $field_has_issue = true;
        return 'Please enter more details.';
    }
}

You could also adapt this code to block certain domains in email addresses.

I implemented this for a client who kept getting requests from a particular legitimate but irrelevant company to “forward this important email” to the Managing Director.

So I just blocked their entire domain by adding it as a strong to my array of blocked values for my email address validation.

D. Identifying empty values

Although most forms have checks to ensure that blank fields can’t be submitted if they’re required fields, I still seem to get submissions with blank fields.

I’m guessing this happens because they’re being submitted by an automated process that bypasses the front-end validation features.

So this code checks that the field value is not empty.

Notes

  • Only apply this to fields that should not be empty
  • Only set the value of $field_has_issue to false ONCE, at the very start of your function that does all of these checks
// Make sure field is not blank
$field_has_issue = false;

if( empty( $field_value ) ) {
    $field_has_issue = true;
}

E. Identifying short values (in PHP)

This is the PHP code that checks the length of an input. (The JavaScript version is further down the page.)

You can choose to apply this check to specific input types (e.g. text or textareas) or specific fields (e.g names or messages). What you can do depends on how much information you can get our of your form plugin.

Notes

  • Again, only set the value of $field_has_issue to false ONCE, at the very start of your function that does all of these checks. You have been warned.
  • The return value here is only for hooks that allow you to add error messages to your form. Again, this depends on the form plugin you’re using.
// Check that field is long enough
$field_has_issue = false;

if( strlen ( $field_value ) <= 2 ){
    $field_has_issue = true;
    return 'Please enter more details.';
}

And here’s a version that checks that a message is long enough.

// Check that message is long enough
$field_has_issue = false;

if( strlen ( $field_value ) <= 20 ){
    $field_has_issue = true;
    return 'Please enter more details.';
}

F. Preventing short inputs in JavaScript

In addition to the above PHP checks on my form inputs, I also implemented a “minimum length” solution in JavaScript for my WPForms forms.

It only applies to fields with the right class, in this case ‘nhs-char-min‘ and pops up a message on the page if the field input is too short.

This was adapted from a WPForms post and tweaked to suit my specific needs. But with the right action hook, it could be applied to any form field from any plugin.

And although it is a JavaScript solution, I still added it via PHP in my custom functions.

/** Set minimum number of characters for fields **/
// Inputs = 4, Textarea = 20
// Only for fields with the wpf-char-min class in Advanced settings
// @link https://wpforms.com/developers/how-to-set-a-minimum-number-of-characters-on-a-text-form-field/
function nhs_wpf_dev_char_min() {
    ?>
    <script type="text/javascript">
        jQuery(document).ready(function ($) {
 
            jQuery( '.nhs-char-min input' ).prop( 'minLength', 4 );
            jQuery( '.nhs-char-min textarea' ).prop( 'minLength', 20 );
 
            jQuery.extend(jQuery.validator.messages, {
                minlength: jQuery.validator.format( "Please enter at least {0} characters" ),
            });
 
 });
    </script>
    <?php
}
add_action( 'wpforms_wp_footer_end', 'nhs_wpf_dev_char_min', 10 );

G. Identifying duplicate values

This code only works if you have access to an action hook or filter that handles the entire submission.

If you only have access to field-level hooks, you won’t be able to run this code on your entry.

This code steps through each input field, checking whether the value already exists in the array.

It then adds the current value to the array and repeats until all fields are processed.

// Check entry for duplicate values
$duplicate_value = false;
$input_values = array();

foreach ( $form_fields as $key => $value ){
    if( in_array( $value, $input_values ) ){
        $duplicate_value = true;
    }
    array_push( $input_values, $value );
}

Pulling it all together

So that’s the end of the examples of the ways you can identify common spammy behaviour in your form submissions.

This is not a complete list, and not everything may work for your situation, but it should give you the general idea and somewhere to start.

And if you have suggestions for other rules or patterns you can identify programatically, please share them in the comments.

To implement this, at a minimum, you’re going to need two functions:

  • nhs_value_contains_blacklisted_term (or similar) – checks whether individual values contain blacklisted terms
  • nhs_check_submission_for_issues (or similar) – runs all form inputs through a selection of checks, as per the above ideas

You may also need to create individual functions for specific field types, depending on the form plugin.

For example, both WPForms and FluentForms have validation hooks at the field and form level, so I did validation field-type by field-type as well as at the overall stage. Doing both is optional if you have a submission-level hook you’re happy to use.

  • nhs_check_input_text
  • nhs_check_input_email
  • nhs_check_input_textarea

You’ll also want to do a ton of test submissions before deploying this into your live site, to make sure it behaves as expected.

I’ll concede that my approach is not entirely effortless and automated, but my experience shows that it is pretty effective.

My workflow now looks like:

  1. Every time I receive an email notification that contains a spammy form submission, I file it in a specific directory in my email program
  2. Once a week or so, I go though these emails and add any unique trigger phrases to a running list I keep in a text editor file, and then delete the email notification of the entry
  3. I then go into the discussion settings of my sites and add all of my recently collected new junky terms to my master list of spam words

It’s not a perfect solution, but since implementing this approach, I receive a lot less spam submissions, which frees me up to deal with the stuff that actually matters.

2. Filtering submissions for common form plugins

Now that you have a plan for your input validations, you have to apply it to your specific form plugin.

I warn you now, this information changes WAY more often than I would like, but it was current as at 13 Feb 2025.

I suggest you check the documentation for your plugin to confirm how your specific form does validation, spam checks and submission processing.

I’m giving you these as examples to help you understand the general approach, so that you can build a solution, no matter which form plugin you’re using.

Ideally, you’d want your form to fail silently when a spam submission fails, so the spammer thinks they’ve succeeded and you never even see their message. (Spam nirvana!)

But not all form plugins have these capabilities. Just keep an eye out for a hook that will allow you to do more than validate, so you can let it validate, and then drop the entry that you’ve identified as spammy before it gets saved or processed.

WPForms

Ah, the bane of my existence. I have a lifetime, unlimited licence from years ago, so I’ve used it everywhere, across tens of sites.

But they keep @#$%^&* removing their action hooks and filters without warning. Grrrrrr.

As of right now, this is what works for this plugin, but it may all change again in 12 months, as is their wont.

I used to be able to make the submission fail silently, when they had the old ‘wpforms_entry_save’ filter available, bit now I just have to show an error message. Sigh.

They now offer a place on each form to add a list of keywords, but there is NO WAY IN HELL I’m updating each individual form each time I collect more spam terms. Nuh-uh.

Field validation

WPForms offers validation action hooks at the field level. So I created one like this for text inputs, name inputs and text areas, changing the core of the function to check whatever was relevant for that field type.

  • add_action( ‘wpforms_process_validate_text’ , ‘nhs_prevent_spam_text’, 10, 3 );
  • add_action( ‘wpforms_process_validate_name’ , ‘nhs_prevent_spam_name’, 10, 3 );
  • add_action( ‘wpforms_process_validate_textarea’ , ‘nhs_prevent_spam_textarea’, 10, 3 );
/** Check text fields for invalid strings **/
function nhs_prevent_spam_text( $field_id, $field_submit, $form_data ) {
    
    if( nhs_value_contains_blacklisted_term( $field_submit ) == true ) {
        echo "Oops"; // allows form to fail silently

        wpforms()->get( 'process' )->errors[ $form_data[ 'id' ] ][ $field_id ] = esc_html__( 'Please check your text.', 'plugin-domain' );
    }
}  
add_action( 'wpforms_process_validate_text' , 'nhs_prevent_spam_text', 10, 3 );
Submission validation

I also created a function that checks the entire entry for backlisted terms and duplicate values, and used the form processing hook to stop those entries.

  • add_action( ‘wpforms_process’, ‘nhs_stop_spam_entry_processsing’, 9, 3 );
/** Stop form processing if it contains spam **/
function nhs_stop_spam_entry_processsing( $fields, $entry, $form_data ) {

    $contains_spam = nhs_check_entry_for_issues( $fields, $entry, $form_data ); // this is a separate function that runs all of my checks on the form inputs
    
    /* If entry has issues, don't save it or send any email notifications */
    if( $contains_spam ){
    //echo "Oops"; // allows form to fail silently (optional)
    wpforms()->process->errors[ $form_data[ 'id' ] ][ 'header' ] = esc_html__( 'Please check your details.', 'wpforms' );
        add_filter( 'wpforms_disable_all_emails', function(){ return true; } );
   }
}
add_action( 'wpforms_process', 'nhs_stop_spam_entry_processsing', 9, 3 );

Gravity Forms

I stopped using Gravity Forms on my own site many years ago because it’s so resource hungry, but many of my clients still use it, and it honestly it’s one of the most powerful form builder plugins I’ve used, especially if you like to tinker with hooks, like I do.

Gravity forms has both field-level validation and form-level validation hooks.

But even better than that, it has a filter for marking a submission as spam.

So all you have to do is write two functions:

  • one main function that does all of your checks for you (example below),
  • a function that checks whether individual values contain blacklisted terms (same as above)

then mark the submission as spam, and then sit back.

function nhs_gforms_check_entry_for_spam( $is_spam, $form, $entry ) {

    // code that checks fields
    foreach( $form['fields'] as $field ){
        // do various validation checks
    }

    return $is_spam;
}
add_filter( 'gform_entry_is_spam', 'nhs_gforms_check_entry_for_spam', 10, 3 );

You can see a full version of my previous solution for that here, including an example of how to access the different field types.

Reduce Gravity Forms spam using common spammy terms via the comment blacklist

The beauty of this is that you can decide how to handle spam submission.

In one client’s case, when the submission was identified as spam, we just showed a simple “thank you” message instead of redirecting them to the usual thank you page, thanks to the power of Gravity Forms hooks.

This kept our lead tracking accurate and left the spammers none the wiser.

Fluent Forms

Fluent Forms has validation at the field level, and also a global validation.

If you want to be able to identify specific fields e.g. names, you’ll want to make sure you edit the field’s advanced options and edit the Name Attribute to be something meaningful that you can detect in code.

Field validation

These are the hooks I used to

  • add_filter(‘fluentform/validate_input_item_input_text’, ‘nhs_check_fluentforms_input_text’, 10, 5);
  • add_filter(‘fluentform/validate_input_item_input_email’, ‘nhs_check_fluentforms_input_email’, 10, 5);
  • add_filter(‘fluentform/validate_input_item_textarea’, ‘nhs_check_fluentforms_input_textarea’, 10, 5);

And here’s an example of the code I used to check text field inputs.

Note that in this one specifically, I checked if the name included “name”, meaning that it was a name input field, and ran my name-specific checks against this value.

/* Check text fields */
function nhs_check_fluentforms_input_text( $errorMessage, $field, $formData, $fields, $form ) {
    
    $field_has_issue = false;

    $field_name = $field['name']; // e.g. name, email, message (ID of input, set in form settings Field > Advanced > Name Attribute )
    $field_value = $formData[$field_name]; // e.g. Nikki (user inputted value)

    // Make sure field is not blank
    if( empty( $field_value ) ) {
        $field_has_issue = true;
    }

    // Validate name text fields
    if( str_contains( $field_name, 'name' ) ){

        // Check if name contains banned characters
        $banned_name_strings = array( "," , "!", "$", "?", "#", "http");

        foreach ( $banned_name_strings as $banned_string ){
            // Clean up string
            $banned_string = trim( $banned_string );
            // Skip empty lines
            if ( empty( $banned_string ) ) {
                continue; }
            // Escape terms so that '#' chars in the spam words don't break things
            $banned_string = preg_quote( $banned_string, '#' );

            $pattern = "#$banned_string#i";
            if ( preg_match( $pattern, $field_value ) ) {
                $field_has_issue = true;
            }
        }
        
        // Check that name is long enough
        if( strlen ( $field_value ) <= 2 ){
            $field_has_issue = true;
            return 'Please enter more details.';
        }
    }

    // Return generic error if there's an issue or it contains a spam term
    if( nhs_value_contains_blacklisted_term( $field_value ) == true || $field_has_issue == true ){
        return 'Please check your details.';
    }

    return [$errorMessage];

}
add_filter('fluentform/validate_input_item_input_text', 'nhs_check_fluentforms_input_text', 10, 5);

I created similar functions to check emails and messages.

Submission validation

Fluent Forms also has a validation hook for the entire form.

  • add_filter(‘fluentform/validation_errors’, ‘nhs_check_fluentform_submission’, 10, 4);

I used this function to check for duplicate values.

I also had trouble with the validation failing even if there were no errors, simply because I was calling this function (and possibly the individual field-level functions), which created an empty array, which was preventing the form from submitting, even though there were technically no errors.

So I added some extra code to allow the form to submit if the error array was actually empty. I have a niggle that this code is not 100% right, but it’s working for me for now, so that will have to do.

* Check overall form data */
function nhs_check_fluentform_submission( $errors, $formData, $form, $fields ){

    // Check that each field value is unique
    $duplicate_value = false;
    $input_values = array();

    foreach ( $formData as $key => $value ){
        if ( str_contains( $key, 'name' ) || str_contains( $key, 'email' ) || str_contains( $key, 'message' ) ){
            if( in_array( $value, $input_values ) ){
                $duplicate_value = true;
            }
            array_push( $input_values, $value );
        }
    }
    if ( $duplicate_value ) {
        $errors[] = esc_html__( 'Please check your submission.', 'fluentforms' );
        //return 'Please check your details.';
    }

    // If no field errors in array, set array to empty string
    $fields_have_errors = false;
    foreach ( $errors as $key => $value ){
        if( $value[0] != '' )
            $fields_have_errors = true;
    }	
    if( $fields_have_errors === false )
        $errors = '';
    
    return $errors;
    //return "Errors: " . print_r( $errors );

 }
 add_filter('fluentform/validation_errors', 'nhs_check_fluentform_submission', 10, 4);

Bricks Builder

I absolutely love Bricks Builder as a powerful, flexible theme, but I only recently tried using their form element.

It’s much more basic compared to dedicated form plugins like Gravity Forms or WPForms, but it does have basic validation and allows you to enter max length values as well.

I think I only just discovered today how to pull the field type out so I can validate based on “name” or “email” or “message” but I’m not going to go back and update my code right now. (I had to add a value to “Attribute: Name” in the field settings.)

It doesn’t offer field-level validation, only form-level validation, and there’s no way to fail silently as far as I can tell. But preventing the submission is all that matters, right?

The tricky part here is pulling out the fields that actually contain values, because the default submission array has all kinds of data in it.

Here’s an example of the structure:

Fields: Array ( 
[form-field-598027] => Test
[form-field-493e5f] => test@example.com
[form-field-f5da2a] => My message
[action] => bricks_form_submit 
[loopId] => 2 
[postId] => 2 
[formId] => iggoer 
[recaptchaToken] => 
[nonce] => 2706176afd 
[referrer] => https://mydomain.com/some-page-name
[urlParams] => {"page_id":"2"} )

Oh, and just so you know, the form ID is taken from the ID on the form element, just the random string after #brxe-. You’ll thank me later.

Now that I’ve figured out how to add the name attribute, this might be easier and allow more sophisticated validation, but what I have is good enough and will have to do for now.

I basically pulled all fields that start with ‘form-field-‘ and only validated those.

Because I couldn’t tell them apart (when I wrote the function), I ran the same checks on all of them – blacklist, length and duplicated values.

/** Prevent Bricks Builder form entry spam **/
function nhs_check_bricks_form_entry_for_issues( $errors, $form ){

    $form_fields   = $form->get_fields();
    $problem_with_entry = false;

    // Check entry for spam terms
    $contains_spam = false;

    foreach ( $form_fields as $key => $value ){
        if ( str_contains( $key, 'form-field-' )  ){
            if( nhs_value_contains_blacklisted_term( $value ) == true )
                $contains_spam = true;
        }
    }
    if ( $contains_spam ) {
        //$errors[] = esc_html__( 'Contains spam!', 'bricks' );
        $problem_with_entry = true;
    }

    // Check entry for too-short values
    $too_short_value = false;

    foreach ( $form_fields as $key => $value ){
        if ( str_contains( $key, 'form-field-' )  ){
            if( strlen( $value ) <= 2 )
                $too_short_value = true;
        }
    }
    if ( $too_short_value ) {
        //$errors[] = esc_html__( 'Too short!', 'bricks' );
        $problem_with_entry = true;
    }

    // Check entry for duplicate values
    $duplicate_value = false;
    $input_values = array();

    foreach ( $form_fields as $key => $value ){
        if ( str_contains( $key, 'form-field-' )  ){
            if( in_array( $value, $input_values ) ){
                $duplicate_value = true;
            }
            array_push( $input_values, $value );
        }
    }
    if ( $duplicate_value ) {
        //$errors[] = esc_html__( 'Duplicate!', 'bricks' );
        $problem_with_entry = true;
    }

    // Return global error
    if ( $problem_with_entry ){
        $errors[] = esc_html__( 'Please check your submission.', 'bricks' );
    }
    
    // Make sure to always return the $errors array
    return $errors;
}
add_filter( 'bricks/form/validate', 'nhs_check_bricks_form_entry_for_issues', 10, 2);

My code may be inefficient, but it gets the job done. Maybe one day I’ll come back and clean it up, but right now I have better things to do with my time.

How to build your own spam filtering code for your form plugin

Now that you have examples of how I’ve solved this problem for four different form plugins, you should be starting to get the idea.

You have enough code examples of both the overall functions and the individual validation checks, that you should be able to generalise it to whatever plugin you’re using.

All you need to figure out is:

  • Field-level validation hooks – Check the documentation
  • Submission-level validation hooks – Check the documentation
  • (Optional) Find an “is_spam” hook – Check the documentation
    • This would be hitting the jackpot, but only Gravity Forms seems to have this one in the plugins I’ve used
    • You might be able to find a hook that fires after validation but before the entry is processed any further
    • If you can, also find hooks that will allow you to stop the entry from being saved AND/OR stop the emails being sent for spam submissions
  • Format of form input field data (so you know how to step through it)
    • Usually I found the easiest way to do this was to echo the field data into the error message, so it spat it out on the page
    • Writing to the server error_log didn’t work for multiple form plugins, probably because of the way that validation works behind the scenes

And if your contact form plugin doesn’t have these kind of hooks, you might want to consider moving to a different one.

How to avoid false positives

Ok, now it’s time to talk about the big ‘effing elephant in the room – false positives.

By which I mean, genuine contacts being blocked by your spam protection measures.

Because the last thing you want is to lose genuine leads, or miss hearing from your customers, because your spam prevention measures were a little too rabid.

Here’s a few things to think about to make sure this rarely, if ever, happens.

1. Think like a human being

As best as you can, put yourself in the shoes of someone who might want to reach out to you.

How might they fill out the form?

What might they say in a message?

What might their inputs typically look like?

You can even go so far as to submit a variety of test entries yourself, or recruit friends or existing customers to test your form for you.

Use these insights to tweak your rules to allow the right messages through.

2. Look at past entries

Somewhere in your list of form entries, buried amongst that dung heap of spam, are some legit form submissions.

You can even look at past enquiries or support emails that came to you via other means.

What matters is that you use these past examples to gain insights into what genuine form submissions might contain, so you can tweak your filtering rules to allow them through.

And yes, that may mean that you have to open yourself up to a little more spam than you’d like.

But to me, the idea of missing out on a genuine communication from a real human being is worth the sacrifice.

Remember, no obstruction of real people?

3. Understand your business

As you receive spam submissions, and start collecting terms for your blacklist, it’s crucial that you keep the specific nature of your business in mind.

If you’re a financial organisation, then you won’t want to add terms like “business loan” or “working capital” to your list.

Of if you’re a digital marketing agency, you’ll want to avoid adding terms like “digital marketing agency” or “SEO services” to your block list. (Sucks to be you!)

The point is, each business has terms that they need to allow, even if they also happen to be markers of spam.

This is something you’ll want to keep this in mind as you continue collecting spam terms over time.

And it might also mean you don’t want to have one blanket list that you use for every business (or client) you have.

Any other thoughts about ways to avoid false positives? Share them below.

Why does contact form spam matter?

You probably don’t need any convincing as to why contact form spam must be stopped, but just in case you have to convince someone else, here’s what to tell them.

1. Harmful links

Spam emails can contain links that cause all kinds of mischief if they’re accidentally (or intentionally) clicked on, including installing trojans, viruses and malware, spoofing account logins, verifying that your email address is actually valid, and generally compromising security.

2. Denial of service

I recently had the pleasure of a spammer using my email signup form to send a spam email to their list of contacts, one every minute for EIGHT HOURS.

I was asleep at the time, and woke up to hundreds of failed emails (thanks to the sending limits at my SMTP service), and a completely broken mail service on my website.

That was how I figured out that WPForms had changed their filter hook on me YET AGAIN!

So I had to disable my form, delete hundred of emails from my queue, and wait 24 hours before anything would work properly again.

And if they hammer your site hard enough, it can bring the entire thing down around your ears, stopping anything and everything from working.

3. Productivity

Dealing with form spam submissions is such as waste of time and resources, and probably the one that pisses us off the most.

Waking up every morning to an inbox full of junk is not how any of us want to start the day.

I have spam filtering rules in place in my email program, and my form submission protections in place, but it’s still not perfect.

What matters is finding solutions that minimise the amount of time that dealing with spam takes up.

4. Missed leads

What’s even worse is that in the deluge of spam, we can miss genuine enquiries and leads, and we learn to see all website form emails as spam, even when they’re not.

We need to severely reduce the noise so we can actually see the signal.

5. Email server issues

Whether you use your own server or a third-party service, getting hammered with spam form submissions can really hurt.

It may lead to limits being placed on your account, and it may also damage your sender reputation overall, making genuine emails less likely to get through in the future.

And this can happen even if you’re just getting and sending occasional spammy form submissions over the course of a normal day.

6. Account hacking

Of course, these spammy contact entries can also lead to accounts being hacked, either through malicious links or cleverly crafted form inputs.

This can give spammers access to important financial information or other key personal and business data, which is never good for business.

7. Query injections

When form submissions are not properly vetted, they can be used by technically-adept hackers to gain access to your website.

The right form, when paired with exactly the right data, can sometimes write inappropriate code directly to your database, giving hackers full control of your website.

So spam form submissions are not something to be taken lightly!

If you give this a go, I’d love to hear how it goes for you.

I’m especially interested in receiving hard data on the impact it has on your business.

And as always, please leave any questions or suggestions in the comments below.

Note: I am not available to provide individual support with implementing this code unless you wish to pay me a boatload of cash. Making it work for your site is up to you.

Nikki H Stokes

Nikki H Stokes

I’ve had a love affair with systems, technology and data for as long as I can remember. I’ve been building websites for over 20 years, running online businesses for more than 15, and teaching myself how to use gazillions of software programs since the very first moment I got my hands on a computer. I’m a geek and proud of it!

Leave the first comment