How to add the custom validation message for a form in CraftCMS
Home » BLOG » Web development » How to add the custom validation message for a form in CraftCMS

How to add the custom validation message for a form in CraftCMS

category:  Web development

A couple of weeks ago, one of my clients asks me how to add the custom validation message for a form in CraftCMS. Today I will share how to do this.

Create a module

In CraftCMS, we will add the custom or override the validation message in a module. Alternatively, you can add the custom validation message via the plugin as well. Creating the module is so simple, please follow on how to build a Module in CraftCMS. Or you can follow on enhancing a CraftCMS3 website with a custom module.

Create a form

The easy way to create the form is using the Entry form from the CraftCMS document. For more information, you can create the front-end user accounts by upgrading to Craft Pro. In this tutorial, I will use the front-end user accounts which request the Craft Pro. However, the custom validation message tutorial should work with any entry form.

Here is an example of the registration form in CraftCMS. The twig code will look like this.

{% macro errorList(errors) %}
  {% if errors %}
    {{ ul(errors, {class: 'errors'}) }}
  {% endif %}
{% endmacro %}

{# `user` is defined if the form returns validation errors. #}
{% set user = user ?? null %}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/save-user') }}
  {{ redirectInput('') }}

  <label for="username">Username</label>
  {{ input('text', 'username', user.username ?? null, {
    id: 'username',
    autocomplete: 'username',
  }) }}
  {{ user ? _self.errorList(user.getErrors('username')) }}

  <label for="firstName">First Name</label>
  {{ input('text', 'firstName', user.firstName ?? null, {
    id: 'firstName',
    autocomplete: 'given-name',
  }) }}
  {{ user ? _self.errorList(user.getErrors('firstName')) }}

  <label for="lastName">Last Name</label>
  {{ input('text', 'lastName', user.lastName ?? null, {
    id: 'lastName',
    autocomplete: 'family-name',
  }) }}
  {{ user ? _self.errorList(user.getErrors('lastName')) }}

  <label for="email">Email</label>
  {{ input('email', 'email', user.email ?? null, {
    id: 'email',
    autocomplete: 'email',
  }) }}
  {{ user ? _self.errorList(user.getErrors('email')) }}

  <label for="password">Password</label>
  {{ input('password', 'password', null, {
    id: 'password',
  }) }}
  {{ user ? _self.errorList(user.getErrors('password')) }}

  <button>Register</button>
</form>

Add the custom validation message

Let’s say, we want to override the generic validation message for the firstName and lastName fields. We also want to add the translation function for the message.

Our Module

In the module, we will add the code in the main module file. Below is an example of module structure.

Modules folder
– MyModule.php

MyModule.php will look like this.

<?php
/**
 * a module for Craft CMS 3.x
 * https://nystudio107.com/blog/enhancing-a-craft-cms-3-website-with-a-custom-module#setting-up-a-site-module
 */

namespace modules;

use Craft;
use craft\base\Element;
use craft\elements\User;
use craft\elements\Entry;
use yii\base\Event;

/**
 * Class MyModule
 *
 * @author    Apple Rinquest
 * @package   MyModule
 * @since     1.0.0
 *
 */
class MyModule extends Module
{
    /**
     * Initializes the module.
     */
    public function init()
    {
        // Set a @modules alias pointed to the modules/ directory
        Craft::setAlias('@modules', __DIR__);

        // Set the controllerNamespace based on whether this is a console or web request
        if (Craft::$app->getRequest()->getIsConsoleRequest()) {
            $this->controllerNamespace = 'modules\\console\\controllers';
        } else {
            $this->controllerNamespace = 'modules\\controllers';
        }

        parent::init();

        // Custom initialization code goes here...

        // if the request is made from the console, we will stop and won't continue further.
        if (Craft::$app->getRequest()->getIsConsoleRequest()) {
            return;
        }        

        // get the request query string for the specific page we want.
        $GET_querystring = Craft::$app->getRequest()->getQueryString();  // it will return with p=slug

        // the request is from the user signup form
        if ($GET_querystring === 'p=signup') {

            Event::on(User::class, Element::EVENT_BEFORE_VALIDATE, function(Event $event) {
                /** @var User $user */
                $user = $event->sender;

                
                    // validate the default user fields
                    if( !$user->firstName || !$user->lastName ) {
                        $event->isValid = false;
                    }
                    if(!$user->firstName ) {
                        $event->sender->addError('firstName', Craft::t('site','First name cannot be blank'));
                    }
                    if(!$user->lastName) {
                        $event->sender->addError('lastName', Craft::t('site','Last name cannot be blank'));
                    }
                    if (strstr($user->firstName, $user->lastName) !== false || strstr($user->lastName, $user->firstName) !== false) {
                        $event->isValid = false;
                        $event->sender->addError('firstName', Craft::t('site', 'Invalid name'));
                        $event->sender->addError('lastName', Craft::t('site', 'Invalid name'));
                    }

                    // you can validate the custom user fields as shown below
                    //
 
                    // if( !$user->phoneNumber ) {
                    //     $event->isValid = false;
                    // }
                    // if(!$user->phoneNumber) {
                    //     $event->sender->addError('phoneNumber', Craft::t('site','Phone number cannot be blank'));
                    // }


            });  // Event ends
        } // $GET_querystring ends

    }  // init function ends
}

Explanation

Working in module

In the main module file, we add our code in the init function which initializes the module. We check the request before continuing. If the request comes from the console(command line), we will stop and won’t continue the execution further. Meaning that the custom validation message will work at the backend(cPanel) and frontend. In CraftCMS, you can filter the console request, backend request, and frontend request.

Check the specific page by query string

After checking the request, we will check the query string from the request. If the query string is “signup“, we will continue our code.

Check the specific page by section name

Tips, for the Entry form, if you don’t want to check the specific page by query string, you check the specific page by section. Here is an example.

        Event::on(Entry::class, Element::EVENT_BEFORE_VALIDATE, function(Event $event) {
            /** @var Entry $entry */
            $entry = $event->sender;

            switch ($entry->getSection()->name) {
                case 'Stores':
                    $validator = new UrlValidator();
                    if ($validator->validateValue($entry->websiteUrl) !== null) {
                        $event->isValid = false;
                        $event->sender->addError('websiteUrl', Craft::t('site',"Please specific https:// or http:// with your website URL"));
                    }
                    break;
            }
        });

Using Event

We capture the EVENT_BEFORE_VALIDATE event then we override the custom validation message for the fields we want. In CraftCMS, you can use the Event Code Generator to generate the boilerplate code for you. Below is an example of the Event Code Generator.

Event Generator in CraftCMS

It is a boilerplate code. You will need to change the code to fit your needs.

Validation Message

In the Event function, we first stop the validation by isValid as false. Then we add the custom validation message via addError(). You can use the translation function( Craft::t() ) if your site can translate more than one language.

On the frontend form, the error can be displayed via errorList macro. Please see the registration form above. The custom validation message will work at the backend(cPanel) as well because in our module, we filter only the console request(command line) out.

In CraftCMS, you can create the custom field as much as you want. You can also add the custom validation message for your custom field as well. By default, the validation message will be generated based on the input field type. In the MyModule.php, I leave the sample of the phoneNumber field which is a custom field of the Users table. Adding the custom validation message will do the same as the example code above.

Useful Links

  • Handing Entry Saves – Learn how to handle the entry saves (draft, revision, published entry)
  • Common Event Flows – Learn which Event you should use in each scenario
  • Using Events in a Custom Module – You can customize Craft’s behavior by listening for any of the events it triggers and introducing your own logic.
  • Yii 2.0 – CraftCMS built on top of Yii 2.0. Often I find myself reading the Yii 2.0 doc to solve my issue while I am working on CraftCMS customization. Since I am a Yii2.0 developer as well, working on CraftCMS is a fun project. But if you are new to Yii 2.0 and CraftCMS, it may take time to understand their architecture. Oh, I am also a WordPress developer. So I combine the template inherit concept from WordPress to CraftCMS. It makes the CraftCMS project has a good template structure and easy to customize and maintain.

Wrap up

Working on the frontend form, often you want to manage the validation message especially if the site can translate more than one language. I hope my post will be useful and save you time. If it does, please consider buying me a cup of coffee and make me smile.