In this month’s edition of “well-built plugins”, we’re taking a look at Contact Form 7 for WordPress. Specifically, we’ll be customizing validation error messages on the front end. The popular contact form builder plugin is quite easy to extend, so let’s jump right in.
What’s With All The Form Plugins?
Form building has become much more ingrained in website development and management these days. Back in the day, we’d usually just have a contact form on a single, dedicated page. A few years later when newsletter subscriptions became popular, we started adding small forms to handle user signups.
These days, however, “calls to action”, or CTAs, are all the rage, and forms everywhere. Product demos, white paper downloads, surveys, and opt-ins are just a few examples of where funneling might require a form for the user to fill out.
Given the variety of CTAs and ever-changing engagement goals, clients need to be able to quickly add, edit, remove forms on their site with minimal developer involvement. In the case of WordPress, a form building plugin fits the bill.
Many Great Options
In the WordPress ecosystem, there are a few “go-to” plugins for creating forms within the admin dashboard. WP Forms, Gravity Forms, Formidable, and Ninja Forms come packed with a lot of features, and they are very user friendly. However, for simpler forms, I sometimes use Contact Form 7(CF7).
CF7 is relatively lightweight when compared to the other three form builders I mentioned above. It comes with the most needed functionality, including form insertion with shortcodes, without the larger codebase required for a fancy UI or front-end sprawl.1With some minor tweaking. You should disable CF7’s default loading of JS/CSS on every page, and enqueue the style and script files only on those pages having CF7 forms. In my mind, unless you have a specific use-case necessitating the installation of a more robust form builder, stick with something simpler, like CF7.
Customizing CF7
Getting back to the original purpose of this article, I was recently tasked with debugging and extending some CF7 forms. In this instance, the client wanted more contextual error messages when a user’s submission failed validation.
Out of the box, the plugin allows for customized validation messages for each field type under the “Messages” tab. Here you can set specific error message for specific error conditions. However, these settings are global. If two required text inputs, first name and last name, are not filled out by the user, they will both show the same error message when validation fails: “The field is required.”
CF7 makes a filter available for each form field type(text, email, tel, etc.) to be used during validation. Usually, you can use the filter to modify the validation itself. For example, you might require a username with a length greater than 8 characters, or a password of a minimum strength2Bonus points if you check the password against public lists of compromised passwords., or a specific TLD in an email address, and so on. We, on the other hand, simply want to modify the messages being sent back when a required field is submitted with an empty value.
In my most recent dev work with CF7, the client requested that each required field should have its own distinct message when invalid. The resulting messages should be “The first name field is required.” and “The last name field is required.” We’ll do just that by adding some custom code to our theme’s3Or child theme, if you’ve followed me long enough and are doing it “properly”. functions.php file, utilizing the one of the filters I spoke of above. We’ll start with a simple example: a username.
<?php
add_filter( 'wpcf7_validate_text*', 'cf7_custom_username_required_validation_message', 1, 2 );
function cf7_custom_username_required_validation_message( $result, $tag ) {
$name = $tag->name;
$value = isset( $_POST[$name] ) ? trim( wp_unslash( (string) $_POST[$name] ) ) : '';
if ( $tag->is_required() && empty( $value ) ) {
if ( preg_match( "/^\*\s(.+)/i", $tag->labels[0], $label_text ) ) {
$result['valid'] = false;
$result['reason'] = array( $name => sprintf( __( "%s is required!", 'text-domain' ), $label_text[1] ) );
}
}
return $result;
}
?>
On line 2, we’re hooking in to the wpcf7_validate_text*
filter. This filter allows us to modify the validation of text fields. The *
at the end means that we only want to hook in to required text fields. However, you can omit the *
to hook in to all text fields in a form. We want to hook in early with a priority of 1 and pass two arguments to our filter funciton. Those two arguments are $result
and $tag
, and we need both for our filtering to work.
The $result
parameter is an array, passed along from filter to filter, carrying the validation result information. We need to ensure we return this array when we are finished filtering it. Simplified, $result['valid']
is a boolean that indicates whether validation was successful or not, and $result['reason']
would contain the actual error message we want to display to the user on the front end.
The $tag
argument is a WPCF7_FormTag
instance that provides all the information about the current form field. We’ll use this to get the field’s name, labels, and ensure it’s a required field in our example.
On line 7, we check the user-submitted value for this field and sanitize it. On line 9, we ensure the field is required and check for an empty value. If both conditions are met, the result should be invalid.
Line 10 performs a REGEX check. This mandates that the field label is in the “* [FIELD_NAME]” format. If the label matches this pattern, we store the “[FIELD_NAME]” section of the string in a $label_text
array. Next, the result is invalidated on line 11, and we the setup the custom validation message on the following line.
Using the matched text from our REGEX, the error message we want to show is formatted as “[FIELD_NAME] is required!”. So, if we have a field named “Username” that fails our validation, the resulting error text would be “Username is required!”. Finally, we make sure to return the $result
.
What’s Next?
The example code above results in all error messages following the “[FIELD_NAME] is required!” format when a required field is submitted with an empty value. You could alter the snippet to check specific required fields or have distinct messages for each field.
Another use-case might be a form with two fields for a user’s email address, with the second being a confirmation of the first. If you perform custom validation similar to the code above, you might check to ensure the both values are equal, but if they fail that test, output a custom validation message using the tag labels for each respective field.
Another thought I had was to give administrators the ability to define messages from the back end. You could store custom messages as a custom post type with various meta for each. You could then query that data during custom validation on the front end. Wildcards could even be used for various, per-field data, like “**FIELD_NAME**” to insert the name of a field in the message, to give form builders maximum flexibility.
Again, this is all possible because the developer of Contact Form 7 added hooks in the plugin’s code. Like documentation, unit testing, etc., it’s much easier to add features like this while the code is being built, instead of after the fact. Even if your plugin or functions.php snippet is small, try to think about how your code’s output might be modified by another user or developer. It makes me happy and my life a whole lot easier.
References
1 | With some minor tweaking. You should disable CF7’s default loading of JS/CSS on every page, and enqueue the style and script files only on those pages having CF7 forms. |
---|---|
2 | Bonus points if you check the password against public lists of compromised passwords. |
3 | Or child theme, if you’ve followed me long enough and are doing it “properly”. |
What if we don’t have labels for fields, only a placeholder? I used this code, but it’s not working for me. Please help.
I can confirm the code definitely works with placeholders on the most recent version of WordPress and CF7. All the “placeholder” form tag does is tell the form to use the default value in the “placeholder” attribute on the HTML element. Try debugging the
$tag
parameter in the filter.Thank you so much, this helped to fix some of my code that wasn’t working before.