Docs Customer Email Verification Customizing the Smart Form: Custom Fields, Validation and Redirects

Customizing the Smart Form: Custom Fields, Validation and Redirects

The Smart Form is the ready-made login and registration form included with Customer Email Verification Pro. It can replace the WooCommerce My Account form, or be placed on any page with the [cev_smart_form] shortcode.

By default the registration step collects an email address and a password, plus a phone number when phone registration is enabled. This page documents the filters and actions available to extend it.

All snippets on this page belong in your child theme’s functions.php file, in a custom plugin, or in a code-snippets plugin.

How the Smart Form Creates an Account

Understanding the order of operations is useful before extending the form. Registration happens in two steps:

  1. The customer submits the form. The plugin validates the submitted values, holds them temporarily, and sends the one-time code. No account is created at this point.
  2. The customer enters the code. Once it is confirmed, the WooCommerce customer account is created and the held values are saved to it.

This ordering means an abandoned verification never leaves a half-created account behind. It also means WooCommerce’s own registration hooks, which run when the account is created, fire during step 2 rather than step 1.

Where step 1’s data actually lives (internal, not a stable API)
Between steps 1 and 2, the submitted values are held in a transient named cev_sf_reg_{md5(strtolower($email))}, expiring after 620 seconds (~10 minutes, matching the OTP window). Its value is an array shaped like {password, phone, phone_e164, phone_shown, use_sms, custom}, where custom holds your registered custom-field values keyed by field name. This is an internal implementation detail, not a documented stable API — it may change in a future version without notice. Option 1 below relies on it because there is currently no supported alternative for reaching this data before the account exists.

Adding Custom Registration Fields

Use the cev_smart_form_custom_fields filter to add fields to the registration step. Requires version 3.0.2 or later.

The array key you use is the WordPress user-meta key the value is saved to. Because first_name, last_name and the billing_* fields are all user-meta keys, naming the field correctly is all that is required for the value to be stored where WooCommerce expects it.

add_filter( 'cev_smart_form_custom_fields', function ( $fields ) {

    $fields['first_name'] = array(
        'label'     => 'First name',
        'type'      => 'text',
        'required'  => true,
        'placement' => 'after_email',
        'priority'  => 10,
    );

    $fields['last_name'] = array(
        'label'     => 'Last name',
        'type'      => 'text',
        'required'  => true,
        'placement' => 'after_email',
        'priority'  => 20,
    );

    return $fields;
} );

A field with options renders as a choice control. The array key is the value saved, and the array value is the label shown:

$fields['billing_country'] = array(
    'label'     => 'Country',
    'type'      => 'select',
    'required'  => true,
    'placement' => 'before_submit',
    'options'   => array(
        'US' => 'United States',
        'GB' => 'United Kingdom',
        'IN' => 'India',
    ),
);

$fields['interests'] = array(
    'label'     => 'What are you interested in?',
    'type'      => 'checkbox_group',
    'placement' => 'before_submit',
    'options'   => array(
        'sale'    => 'Sale alerts',
        'new'     => 'New arrivals',
        'restock' => 'Restock notices',
    ),
);

$fields['terms_accepted'] = array(
    'label'     => 'I agree to the terms and conditions',
    'type'      => 'acceptance',
    'required'  => true,
    'placement' => 'before_submit',
);

checkbox_group lets the customer tick more than one option. The checked values are saved as a PHP array in the single user-meta key you named — WordPress automatically serializes it, so read it back with the ordinary get_user_meta( $user_id, 'interests', true ) and you’ll get the array directly.

Values are validated as the customer fills in the form, held until the one-time code is confirmed, and written to the account only after verification succeeds.

Field Arguments

ArgumentDescription
Array keyThe field key, and also the user-meta key the value is saved to. For example first_name, last_name, billing_company.
labelThe field label shown on the form. Supports a small set of safe HTML — a, br, strong, em, span — commonly used for a single Terms & Conditions link. There’s no hard limit to exactly one link, but keep it simple.
typeInput type. Defaults to text. See the list of supported types below.
requiredSet to true to make the field mandatory. Defaults to false.
placementWhere the field appears in the form. Defaults to before_submit. See the list of positions below.
prioritySort order within the chosen position. Lower numbers appear first. Defaults to 10.
placeholderOptional placeholder text.
optionsRequired for select, radio and checkbox_group. Accepts a simple list, or an array of value to label pairs where the array key is the value saved.

Supported Field Types

text, email, tel, url, number, date, textarea, select, radio, checkbox_group and acceptance (a single checkbox, intended for terms and conditions).

Field Positions

register_top, after_email, after_phone, after_password and before_submit.

Reserved Keys

These keys are used by the form’s own inputs and cannot be used for a custom field: email, password, phone, login, otp, nonce, action, channel and remember.

Acting on Saved Values

To store a value somewhere other than user meta, use the cev_smart_form_custom_fields_saved action. It runs once the account has been created and verified.

add_action( 'cev_smart_form_custom_fields_saved', function ( $user_id, $values ) {
    // $values is keyed by field key, already validated and sanitized.
}, 10, 2 );

Stores With Extra Required WooCommerce Registration Fields

Many stores add required fields to the standard WooCommerce registration form, such as First Name or a company name, using a plugin, a theme template override or a custom snippet. Those rules are enforced through WooCommerce’s registration validation hooks, woocommerce_register_post and woocommerce_registration_errors.

As described above, those hooks run when the account is created, which is after the one-time code is confirmed. If a required field is not part of the Smart Form, that validation has nothing to check against and registration will not complete.

Note that the Smart Form does fire the standard woocommerce_register_form hook, so fields added by a plugin through that hook already render inside it. The two approaches below apply when the fields come from somewhere else, such as a template override or a validation-only snippet.

Option 1: Collect the Field and Pass It Through

Add the field to the Smart Form (using the first_name/last_name block from Adding Custom Registration Fields above), then make its value available to the existing validation, which reads from $_POST. This keeps the store’s validation rules intact.

Writing into $_POST is a workaround, not a general-purpose pattern: at the moment woocommerce_register_post fires, the Smart Form’s own submitted values live in the transient described above, not in $_POST, so third-party validation reading $_POST directly would otherwise find nothing. Priority 1 is used so this runs before other plugins’ own validation on the same hook. Only reach for this if a required field genuinely isn’t part of the Smart Form and you can’t add it there instead.

// Make the pending Smart Form values available to the existing registration validation.
add_action( 'woocommerce_register_post', function ( $username, $email, $errors ) {

    $pending = get_transient( 'cev_sf_reg_' . md5( strtolower( $email ) ) );

    if ( ! is_array( $pending ) || empty( $pending['custom'] ) ) {
        return;
    }

    // Map Smart Form field keys to the $_POST names the validation expects.
    $map = array(
        'first_name' => array( 'first_name', 'billing_first_name' ),
        'last_name'  => array( 'last_name', 'billing_last_name' ),
    );

    foreach ( $pending['custom'] as $key => $value ) {
        $targets = isset( $map[ $key ] ) ? $map[ $key ] : array( $key );

        foreach ( $targets as $target ) {
            if ( empty( $_POST[ $target ] ) ) {
                $_POST[ $target ] = $value;
            }
        }
    }
}, 1, 3 );

Add further fields the same way: repeat the $fields[...] block using the field’s meta key, and add a line to $map if the validation reads it under a different $_POST name.

Option 2: Limit the Existing Validation to the WooCommerce Form

If the extra fields are not needed for Smart Form registrations, the existing validation can be limited to the standard WooCommerce form. The Smart Form still enforces its own required fields, and WooCommerce’s core email and username checks remain in force.

cev_smart_register and cev_smart_verify_register below are not WordPress action hooks you can add your own callback to — they’re the AJAX action identifiers the Smart Form’s own registration requests are submitted under. Checking $_POST['action'] against them is how you detect “this particular request came from the Smart Form” inside a hook that runs for every registration, like woocommerce_registration_errors.

add_filter( 'woocommerce_registration_errors', function ( $errors, $username, $email ) {

    $action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';

    if ( 'cev_smart_verify_register' !== $action && 'cev_smart_register' !== $action ) {
        return $errors;
    }

    $keep = array(
        'registration-error-email-exists',
        'registration-error-invalid-email',
        'registration-error-missing-email',
        'registration-error-username-exists',
        'registration-error-invalid-username',
    );

    $clean = new WP_Error();

    foreach ( $errors->get_error_codes() as $code ) {
        if ( in_array( $code, $keep, true ) ) {
            foreach ( $errors->get_error_messages( $code ) as $msg ) {
                $clean->add( $code, $msg );
            }
        }
    }

    return $clean;
}, 99, 3 );

Changing the Redirect After Login or Registration

By default the Smart Form sends customers to the My Account page after they sign in or complete registration. Use the cev_smart_form_register_redirect filter to change that. The same filter covers all three paths: password login, email one-time-code login, and registration.

add_filter( 'cev_smart_form_register_redirect', function ( $url, $user_id ) {
    return wc_get_page_permalink( 'shop' );
}, 10, 2 );
ParameterDescription
$urlThe default redirect URL, normally the My Account page.
$user_idThe ID of the customer who just signed in or registered.

Returning to the Page the Customer Originally Requested

On a private or members-only store, visitors are typically sent to the login page with a redirect_to parameter recording where they were headed. This version reads that parameter and returns them there, falling back to the Shop page.

add_filter( 'cev_smart_form_register_redirect', function ( $url, $user_id ) {

    $referer = wp_get_referer(); // The page the Smart Form was submitted from.

    if ( $referer ) {
        $query = wp_parse_url( $referer, PHP_URL_QUERY );

        if ( $query ) {
            parse_str( $query, $args );

            if ( ! empty( $args['redirect_to'] ) ) {
                return wp_validate_redirect( urldecode( $args['redirect_to'] ), $url );
            }
        }
    }

    return wc_get_page_permalink( 'shop' );
}, 10, 2 );

Checking Verification Status in Your Own Code

Customers who register through the Smart Form are verified by definition, since the account is only created once the code is confirmed. See Developers for the full is_user_email_verified() reference — most useful here for accounts created another way, such as by an administrator, an import, or a migration.

The example below blocks unverified customers from the shop and product pages and sends them to My Account. Store staff are never affected.

add_action( 'template_redirect', function () {

    if ( is_admin() || ! is_user_logged_in() || ! function_exists( 'cev_pro' ) ) {
        return;
    }

    if ( ! ( is_shop() || is_product() || is_product_category() || is_product_tag() ) ) {
        return;
    }

    $user_id = get_current_user_id();

    if ( user_can( $user_id, 'edit_posts' ) ) {
        return; // Never lock out store staff.
    }

    if ( cev_pro()->function->is_user_email_verified( $user_id ) ) {
        return;
    }

    wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) );
    exit;
}, 5 );

In most cases a snippet is not needed. The setting WooCommerce → Email Verification → Settings → Login Authentication → Require unverified logged-in customers to verify automatically sends a code and shows the verification popup to any unverified customer on their next page load.

Smart Form Filter Reference

FilterParametersSinceDescription
cev_smart_form_custom_fields$fields (array)3.0.2Add custom fields to the registration step.
cev_smart_form_register_redirect$url, $user_idNot documented in codeChange where customers are sent after login or registration.
cev_smart_form_auto_login$auto_login (bool, default true), $user_id2.10.0Return false to stop signing the customer in automatically after registration.
cev_smart_form_password_is_strong$strong (bool), $passwordNot documented in codeOverride the password strength rule applied to new registrations. Return true/false; $password is the plaintext value being checked, not stored.
cev_smart_form_fire_wc_hooks$fire (bool, default true)Not documented in codeReturn false to stop the Smart Form firing the standard WooCommerce login and registration form hooks, which are used by captcha and security plugins.

Smart Form Action Reference

ActionParametersDescription
cev_smart_form_custom_fields_saved$user_id, $values (array, keyed by field key)Fires once the account is created and verified. Use it to store a custom-field value somewhere other than user meta.