PHP Hooks and Filters Reference

Reference for the PHP actions and filters Elzo Forms provides for submissions, fields, templates, modules, and PRO automations.

Free & PRO · Developer Last reviewed

This page lists the main PHP actions and filters available in Elzo Forms. Use these hooks when you need to validate submissions, detect spam, customize saved data, modify email settings, change AJAX responses, register modules, customize templates, or extend PRO automations.

This is a developer reference. If you are looking for task-based examples, see the related developer guides for custom fields, modules, template overrides, JavaScript events, and custom automation actions.


How Elzo Forms processes a submission

Most submission-related hooks run in this order:

  1. The form submission request is received.
  2. Nonce, form ID, form object, timing, and IP checks are validated.
  3. elzo_forms_validate_submission runs for custom server-side validation.
  4. elzo_forms_detect_spam runs for custom spam detection.
  5. Submitted fields are matched against visible form fields.
  6. elzo_forms_is_field_value_empty runs for required-field empty checks.
  7. Field classes validate and sanitize field values.
  8. The submission is saved.
  9. elzo_forms_after_submission_saved runs for non-spam submissions.
  10. Enabled modules handle the submission.
  11. elzo_forms_submission_email_settings runs before the built-in email notification is sent.
  12. elzo_forms_submission_response runs before the final AJAX success response is returned.

Important: use the earliest hook that matches your goal. For example, use elzo_forms_validate_submission to block a submission, elzo_forms_after_submission_saved to run logic after saving, and elzo_forms_submission_response to customize the frontend success response.


Quick reference

Hook Type Scope Use it to
elzo_forms_validate_submission Filter Free Run custom server-side validation before field processing continues.
elzo_forms_detect_spam Filter Free Mark a submission as spam using custom rules.
elzo_forms_is_field_value_empty Filter Free Customize how Elzo Forms decides whether a required field is empty.
elzo_forms_after_submission_saved Action Free Run custom logic after a valid non-spam submission is saved.
elzo_forms_submission_email_settings Filter Free Change built-in email notification settings at runtime.
elzo_forms_submission_response Filter Free Customize the AJAX success response returned to the frontend.
elzo_forms_field_types Filter Free Register or modify available field types.
elzo_forms_field Filter Free Modify field data before rendering.
elzo_forms_field_type_variants Filter Free Give a field type variants, stored as base:variant.
elzo_forms_field_picker_items Filter Free Set the group, icon and search terms of field types in the builder.
elzo_forms_is_text_field_value_valid Filter Free Validate the format of a Text variant.
elzo_forms_block_form_options Filter Free Change which forms the Elzo Form block offers.
elzo_forms_content_search_query_args Filter Free Limit the content offered by the admin page pickers.
elzo_forms_form_data_attributes Filter Free Add custom HTML data attributes to the rendered form element.
elzo_forms/templates/* Action / Filter Free Customize template loading, template arguments, and before/after template output.
elzo_forms/modules/register Action Free Register a custom module with the module manager.
elzo_forms_pro_register_automation_extensions Action PRO Register custom automation actions, condition operators, controls, and data providers.
elzo_forms_pro_automation_before_action Action Pro Run logic immediately before an automation action executes.
elzo_forms_pro_automation_after_action Action Pro Inspect or react to an automation action result.

Submission hooks

elzo_forms_validate_submission

Type: Filter
Scope: Free
Runs: after the form object and timing checks are prepared, before submitted fields are processed.
Use this to: block a submission with custom server-side validation.

Signature:

add_filter( 'elzo_forms_validate_submission', function( $result, $form, $context ) {
    return $result;
}, 10, 3 );

Parameters:

  • $result — default validation result. Usually true.
  • $form — current \ElzoForms\Form\Form instance.
  • $context — array with request timing data, including submission_time and form_load_time.

Return: return the current result to continue, or return a WP_Error to stop the submission and show an error message.

Example: block submissions outside business hours.

<?php
add_filter( 'elzo_forms_validate_submission', function( $result, $form, $context ) {
    $hour = (int) current_time( 'G' );

    if ( $hour < 9 || $hour >= 18 ) {
        return new WP_Error(
            'elzo_forms_closed',
            __( 'This form is available only during business hours.', 'my-plugin' )
        );
    }

    return $result;
}, 10, 3 );

Note: do not return false expecting the submission to stop. To block processing, return a WP_Error.


elzo_forms_detect_spam

Type: Filter
Scope: Free
Runs: before submitted fields are processed and before the submission is saved.
Use this to: mark a submission as spam based on custom rules.

Signature:

add_filter( 'elzo_forms_detect_spam', function( $is_spam, $form, $context ) {
    return $is_spam;
}, 10, 3 );

Parameters:

  • $is_spam — current spam status as boolean.
  • $form — current \ElzoForms\Form\Form instance.
  • $context — array with timing data.

Return: boolean. Return true to mark the submission as spam.

Example: mark submissions from a suspicious query string as spam.

<?php
add_filter( 'elzo_forms_detect_spam', function( $is_spam, $form, $context ) {
    if ( isset( $_GET['traffic_source'] ) && $_GET['traffic_source'] === 'bad-campaign' ) {
        return true;
    }

    return $is_spam;
}, 10, 3 );

elzo_forms_is_field_value_empty

Type: Filter
Scope: Free
Runs: when Elzo Forms checks whether a visible required field is empty.
Use this to: customize empty-value detection for special field values.

Signature:

add_filter( 'elzo_forms_is_field_value_empty', function( $is_empty, $value, $field, $form ) {
    return $is_empty;
}, 10, 4 );

Parameters:

  • $is_empty — current empty check result.
  • $value — submitted field value.
  • $field — current field object.
  • $form — current form object.

Return: boolean. Return true when the value should be treated as empty.

Example: treat a placeholder option value as empty.

<?php
add_filter( 'elzo_forms_is_field_value_empty', function( $is_empty, $value, $field, $form ) {
    if ( $value === 'please-select' ) {
        return true;
    }

    return $is_empty;
}, 10, 4 );

elzo_forms_after_submission_saved

Type: Action
Scope: Free
Runs: after a valid non-spam submission is saved, before built-in email settings are applied and before the final response is sent.
Use this to: send data to external services, create posts, update metadata, or trigger custom business logic after a successful submission.

Signature:

add_action( 'elzo_forms_after_submission_saved', function( $form, $submission, $submission_post_id ) {
    // Your logic here.
}, 10, 3 );

Parameters:

  • $form — current \ElzoForms\Form\Form instance.
  • $submission — current \ElzoForms\Submission\Submission instance.
  • $submission_post_id — saved submission post ID.

Example: store a custom meta flag on every saved submission.

<?php
add_action( 'elzo_forms_after_submission_saved', function( $form, $submission, $submission_post_id ) {
    update_post_meta( $submission_post_id, '_sent_to_custom_integration', 'no' );
}, 10, 3 );

Example: read submitted fields.

<?php
add_action( 'elzo_forms_after_submission_saved', function( $form, $submission, $submission_post_id ) {
    $fields = $submission->get_fields();

    foreach ( $fields as $field ) {
        $field_id = $field['id'] ?? '';
        $value    = $field['value'] ?? '';

        // Use $field_id and $value here.
    }
}, 10, 3 );

elzo_forms_submission_email_settings

Type: Filter
Scope: Free
Runs: before the built-in email notification is sent.
Use this to: enable, disable, or change built-in notification recipients at runtime.

Signature:

add_filter( 'elzo_forms_submission_email_settings', function( $email_settings, $form, $submission, $submission_post_id ) {
    return $email_settings;
}, 10, 4 );

Parameters:

  • $email_settings — array of email notification settings.
  • $form — current form object.
  • $submission — current submission object.
  • $submission_post_id — saved submission post ID.

Return: modified email settings array.

Common keys:

  • email_notificationsyes or no.
  • email_notification_recipients — comma-separated string, newline-separated string, or array of email addresses.

Example: send notifications for a specific form to a different recipient.

<?php
add_filter( 'elzo_forms_submission_email_settings', function( $email_settings, $form, $submission, $submission_post_id ) {
    if ( method_exists( $form, 'get_slug' ) && $form->get_slug() === 'partner-request' ) {
        $email_settings['email_notifications'] = 'yes';
        $email_settings['email_notification_recipients'] = 'partners@example.com';
    }

    return $email_settings;
}, 10, 4 );

elzo_forms_submission_response

Type: Filter
Scope: Free
Runs: after submission processing, before the AJAX success response is returned.
Use this to: add custom response data, adjust redirects, or pass custom data to frontend scripts.

Signature:

add_filter( 'elzo_forms_submission_response', function( $response, $submission, $form ) {
    return $response;
}, 10, 3 );

Parameters:

  • $response — response array.
  • $submission — current submission object.
  • $form — current form object.

Default response keys:

  • message
  • redirect_url
  • redirect_delay
  • submission_post_id

Example: add a custom tracking value to the AJAX response.

<?php
add_filter( 'elzo_forms_submission_response', function( $response, $submission, $form ) {
    $response['tracking_event'] = 'elzo_form_submission';
    $response['form_id'] = method_exists( $form, 'get_id' ) ? $form->get_id() : null;

    return $response;
}, 10, 3 );

Field and form rendering hooks

elzo_forms_field_types

Type: Filter
Scope: Free
Runs: when Elzo Forms builds the list of available field types.
Use this to: add or modify field type definitions.

Signature:

add_filter( 'elzo_forms_field_types', function( $types ) {
    return $types;
} );

Return: array of field type definitions. Each field type should include a human-readable label and a PHP class name in class.

Example: register a custom field type.

<?php
add_filter( 'elzo_forms_field_types', function( $types ) {
    $types['rating'] = [
        'label' => __( 'Rating', 'my-plugin' ),
        'class' => \MyPlugin\ElzoForms\Fields\Rating_Field::class,
    ];

    return $types;
} );

elzo_forms_field_text_subtypes

Type: Filter
Scope: Free
Use this to: add or modify the variants of the Text field. The defaults are text, date, time, number, email, url, tel, and password. A variant is stored in the field type as text:{key}, gets its own entry in the builder’s field type list, and its key becomes the type attribute of the rendered input. A variant you add has no format check until you give it one with elzo_forms_is_text_field_value_valid.

<?php
add_filter( 'elzo_forms_field_text_subtypes', function( $subtypes ) {
    $subtypes['color'] = __( 'Color', 'my-plugin' );

    return $subtypes;
} );

elzo_forms_field_type_variants

Type: Filter
Scope: Free
Runs: whenever Elzo Forms resolves the variants of a base field type.
Arguments: $variants — labels keyed by variant; $base_type — the base field type. Text’s variants arrive here from elzo_forms_field_text_subtypes.
Use this to: give any field type variants. A variant is stored as {base type}:{variant}. The variant keyed like the base type, or else the first one, is the default and is stored as the bare base type. Keys may contain only letters, digits, _ and -; the field class reads its variant with get_subtype().

<?php
add_filter( 'elzo_forms_field_type_variants', function( $variants, $base_type ) {
    if ( 'star_rating' === $base_type ) {
        $variants = [
            'star_rating' => __( 'Star rating', 'my-plugin' ),
            'hearts'      => __( 'Heart rating', 'my-plugin' ),
        ];
    }

    return $variants;
}, 10, 2 );

Return: array of labels keyed by variant.


elzo_forms_field_picker_items

Type: Filter
Scope: Free
Runs: when the builder prepares the field type list opened by Add Field and by each field’s Type button.
Arguments: $items — a list of items with the keys type, label, category, icon and keywords. Types without built-in presentation arrive with type only.
Use this to: set the group, icon, label and search terms of a field type. Items naming a type that is not registered are dropped, so the list can never offer a type the builder cannot create. The accepted categories and icons are listed in Field Type Filters.

<?php
add_filter( 'elzo_forms_field_picker_items', function( $items ) {
    foreach ( $items as &$item ) {
        if ( 'star_rating' === $item['type'] ) {
            $item['category'] = 'choice';
            $item['icon']     = 'range';
            $item['keywords'] = [ 'stars', 'score', 'review' ];
        }
    }
    unset( $item );

    return $items;
} );

elzo_forms_is_text_field_value_valid

Type: Filter
Scope: Free
Runs: during submission validation, for every non-empty value of every Text variant — first on the submitted value, then on the sanitized one.
Arguments: $is_valid — the built-in verdict; $value — the value without surrounding whitespace; $subtype — the variant, such as email or tel; $field — the field data.
Use this to: give a custom Text variant a format, or tighten a built-in one. Returning false rejects the submission with the variant’s error message.

<?php
add_filter( 'elzo_forms_is_text_field_value_valid', function( $is_valid, $value, $subtype, $field ) {
    // Require international phone numbers.
    if ( 'tel' === $subtype && 0 !== strpos( $value, '+' ) ) {
        return false;
    }

    return $is_valid;
}, 10, 4 );

Return: true to accept the value, false to reject it.


elzo_forms_field_widths

Type: Filter
Scope: Free
Use this to: add or modify field width options in the admin UI.

<?php
add_filter( 'elzo_forms_field_widths', function( $widths ) {
    $widths['2/3'] = __( 'Two thirds (2/3)', 'my-plugin' );

    return $widths;
} );

elzo_forms_field

Type: Filter
Scope: Free
Runs: when field data is prepared from a field object.
Use this to: modify field data before rendering.

Signature:

add_filter( 'elzo_forms_field', function( $field_data, $field ) {
    return $field_data;
}, 10, 2 );

Example: add a custom class to a specific field key.

<?php
add_filter( 'elzo_forms_field', function( $field_data, $field ) {
    if ( isset( $field_data['field_key'] ) && $field_data['field_key'] === 'company_email' ) {
        $field_data['custom_class'] = trim( ( $field_data['custom_class'] ?? '' ) . ' is-company-email' );
    }

    return $field_data;
}, 10, 2 );

elzo_forms_form_data_attributes

Type: Filter
Scope: Free
Runs: when the form HTML attributes are prepared.
Use this to: add custom data-* attributes to the form wrapper.

Signature:

add_filter( 'elzo_forms_form_data_attributes', function( $attributes, $form_id ) {
    return $attributes;
}, 10, 2 );

Example: expose a custom integration flag to frontend scripts.

<?php
add_filter( 'elzo_forms_form_data_attributes', function( $attributes, $form_id ) {
    $attributes['data-my-integration-enabled'] = '1';

    return $attributes;
}, 10, 2 );

elzo_forms_handle_phone_field

Type: Filter
Scope: Free
Runs: when a telephone text field is sanitized.
Use this to: normalize telephone values before they are stored.

<?php
add_filter( 'elzo_forms_handle_phone_field', function( $phone, $field_data ) {
    return preg_replace( '/[^\d+]/', '', $phone );
}, 10, 2 );

Block editor and conditional logic hooks

elzo_forms_block_form_options

Type: Filter
Scope: Free
Runs: when the block editor loads, for users who can edit posts.
Arguments: $options — the forms offered by the Elzo Form block, each with id, title, status and statusLabel.
Use this to: remove forms from the block’s form selector. Do not add form settings or content to the entries: the list is printed into the editor page.

<?php
add_filter( 'elzo_forms_block_form_options', function( $options ) {
    return array_values( array_filter( $options, function( $option ) {
        return strpos( $option['title'], '[internal]' ) === false;
    } ) );
} );

elzo_forms_condition_types

Type: Filter
Scope: Free
Runs: when the condition types of a context are built. Field visibility uses the context field.
Arguments: $types — labels keyed by condition type; $usage_context.
Use this to: remove a condition type from the builder. Rules already saved with that type keep working. Do not add types here: Elzo Forms evaluates only its own condition types, and a rule of any other type never matches.

<?php
add_filter( 'elzo_forms_condition_types', function( $types, $usage_context ) {
    if ( 'field' === $usage_context ) {
        unset( $types['page'] );
    }

    return $types;
}, 10, 2 );

elzo_forms_condition_operators_map

Type: Filter
Scope: Free
Arguments: $operators — lists of operator keys, keyed by condition type; $usage_context.
Use this to: remove operators a condition type offers in the builder. The same rule as for types applies: an operator Elzo Forms does not implement never matches.


elzo_forms_condition_operator_labels

Type: Filter
Scope: Free
Arguments: $labels — labels keyed by operator; $usage_context.
Use this to: rename operators in the condition builder.


elzo_forms_condition_type_picker_items

Type: Filter
Scope: Free
Arguments: $items — the entries of the condition type list, with type, label, icon and keywords; $registered — labels of the registered condition types.
Use this to: change how condition types are presented: label, icon and search terms. It changes presentation only — an entry can be chosen only when its type is registered.


elzo_forms_content_post_types

Type: Filter
Scope: Free
Arguments: $post_types — post type slugs. The default is every public post type except attachments.
Use this to: choose the post types the admin content pickers offer and search — the page and post type values of Page conditions, and in PRO the page picker of automation conditions. An empty result falls back to pages.

<?php
add_filter( 'elzo_forms_content_post_types', function( $post_types ) {
    return [ 'page', 'product' ];
} );

elzo_forms_content_search_query_args

Type: Filter
Scope: Free
Arguments: $query_args — the WP_Query arguments; $contextsearch, include, post_types, limit and page.
Use this to: keep content out of the admin content pickers with ordinary query arguments such as post__not_in or tax_query. Entries that are not published in a public post type are offered only to users who can edit them, whatever the query returns.

<?php
add_filter( 'elzo_forms_content_search_query_args', function( $query_args, $context ) {
    $query_args['post__not_in'] = [ (int) get_option( 'page_on_front' ) ];

    return $query_args;
}, 10, 2 );

Settings and saved form data hooks

elzo_forms_default_settings

Type: Filter
Scope: Free
Use this to: add or modify default form settings.

<?php
add_filter( 'elzo_forms_default_settings', function( $settings ) {
    $settings['my_custom_setting'] = '';

    return $settings;
} );

elzo_forms_form_settings

Type: Filter
Scope: Free
Runs: after global, default, and form-specific settings are merged.
Use this to: modify runtime form settings.

<?php
add_filter( 'elzo_forms_form_settings', function( $settings, $form_post ) {
    if ( $form_post instanceof WP_Post && $form_post->post_name === 'contact-us' ) {
        $settings['min_submission_delay'] = 3;
    }

    return $settings;
}, 10, 2 );

elzo_forms_form_styles

Type: Filter
Scope: Free
Use this to: modify runtime form style settings.

<?php
add_filter( 'elzo_forms_form_styles', function( $styles, $form_post ) {
    $styles['primary_color'] = '#1d4ed8';

    return $styles;
}, 10, 2 );

elzo_forms_form_texts

Type: Filter
Scope: Free
Use this to: modify runtime form text values.

<?php
add_filter( 'elzo_forms_form_texts', function( $texts, $form_post ) {
    $texts['success_submit_message'] = __( 'Thank you. We received your message.', 'my-plugin' );

    return $texts;
}, 10, 2 );

elzo_forms_form_data_before_save

Type: Filter
Scope: Free / Pro
Runs: before the form builder JSON data is saved to the form post content.
Use this to: add extension data to the saved form JSON payload.

Signature:

add_filter( 'elzo_forms_form_data_before_save', function( $data, $post_id, $raw_post ) {
    return $data;
}, 10, 3 );

Example: persist custom builder data.

<?php
add_filter( 'elzo_forms_form_data_before_save', function( $data, $post_id, $raw_post ) {
    if ( isset( $raw_post['my_plugin_form_meta'] ) ) {
        $data['my_plugin'] = [
            'meta' => sanitize_text_field( wp_unslash( $raw_post['my_plugin_form_meta'] ) ),
        ];
    }

    return $data;
}, 10, 3 );

Important: only store data you own under a unique top-level key to avoid conflicts with Elzo Forms core data.


Admin save filters

These filters are useful when extending the form builder or normalizing saved form configuration.

Hook Type Arguments Use it to
elzo_forms_field_before_save Filter $field Modify a field array before it is stored.
elzo_forms_step_before_save Filter $step Modify a step array before it is stored.
elzo_forms_form_settings_value_before_save Filter $value, $key Modify any form setting value before saving.
elzo_forms_form_settings_{$key}_before_save Filter $value Modify one specific form setting before saving.
elzo_forms_form_texts_value_before_save Filter $value, $key Modify any form text value before saving.
elzo_forms_form_texts_{$key}_before_save Filter $value Modify one specific form text before saving.
elzo_forms_json_encoding_error Action $exception, $post_id Handle JSON encoding failures while saving a form.

Template hooks

Elzo Forms supports WooCommerce-style template overrides and template-loading hooks.

elzo_forms/templates/theme_dir

Type: Filter
Scope: Free
Use this to: change the theme directory where Elzo Forms looks for overridden templates. The value is a folder name relative to the theme root; the default is elzo-forms. Register the filter anywhere that runs before a form is rendered, including a theme’s functions.php.

<?php
add_filter( 'elzo_forms/templates/theme_dir', function( $dir ) {
    return 'my-theme-forms';
} );

elzo_forms/templates/locate_template

Type: Filter
Scope: Free
Use this to: override the located template path programmatically.

<?php
add_filter( 'elzo_forms/templates/locate_template', function( $template, $template_name, $template_path, $args ) {
    if ( $template_name === 'form.php' ) {
        $custom_template = plugin_dir_path( __FILE__ ) . 'templates/elzo-form.php';

        if ( file_exists( $custom_template ) ) {
            return $custom_template;
        }
    }

    return $template;
}, 10, 4 );

elzo_forms/templates/template_args

Type: Filter
Scope: Free
Use this to: add or modify variables available inside frontend templates.

<?php
add_filter( 'elzo_forms/templates/template_args', function( $args, $template_name, $template ) {
    if ( $template_name === 'form.php' ) {
        $args['my_custom_value'] = 'Example value';
    }

    return $args;
}, 10, 3 );

elzo_forms/templates/before_template and elzo_forms/templates/after_template

Type: Action
Scope: Free
Use this to: output or run logic before or after a frontend template is loaded.

<?php
add_action( 'elzo_forms/templates/before_template', function( $template_name, $template_path, $template, $args ) {
    if ( $template_name === 'form.php' ) {
        echo '<div class="my-elzo-form-wrapper">';
    }
}, 10, 4 );

add_action( 'elzo_forms/templates/after_template', function( $template_name, $template_path, $template, $args ) {
    if ( $template_name === 'form.php' ) {
        echo '</div>';
    }
}, 10, 4 );

Module hooks

elzo_forms/modules/register

Type: Action
Scope: Free
Runs: after the core module manager is created and the core modules are registered.
Use this to: register custom modules.

Signature:

add_action( 'elzo_forms/modules/register', function( $manager ) {
    // $manager->register( new Your_Module() );
} );

Example:

<?php
add_action( 'elzo_forms/modules/register', function( $manager ) {
    if ( class_exists( \MyPlugin\ElzoForms\Modules\My_Module::class ) ) {
        $manager->register( new \MyPlugin\ElzoForms\Modules\My_Module() );
    }
} );

elzo_forms/modules/enabled

Type: Filter
Scope: Free
Use this to: modify the list of enabled modules for a specific form.

<?php
add_filter( 'elzo_forms/modules/enabled', function( $enabled, $form_id ) {
    if ( (int) $form_id === 123 ) {
        $enabled[] = 'my_module';
    }

    return array_values( array_unique( $enabled ) );
}, 10, 2 );

elzo_forms/module/{$id}/settings

Type: Filter
Scope: Free
Use this to: modify a specific module settings array for one form.

<?php
add_filter( 'elzo_forms/module/my_module/settings', function( $settings, $form_id ) {
    $settings['enabled_for_custom_flow'] = true;

    return $settings;
}, 10, 2 );

JSON storage hooks

Use these hooks when integrating with JSON-based form sync, deployment workflows, or custom import/export tools.

Hook Type Arguments Use it to
elzo_forms/json_storage/paths Filter $paths Change the directories scanned for form JSON files.
elzo_forms/load_form_data Filter $data, $post Modify loaded form data, including JSON-file-backed data.
elzo_forms_form_compatibility_warning Action $warning, $result, $form_id React to a JSON form that requires a newer plugin version.
elzo_forms/json_storage/import_form_payload Filter $payload, $form_data, $filepath Sanitize or modify imported form payload data.

Example: strip an environment-specific value from an imported form.

<?php
add_filter( 'elzo_forms/json_storage/import_form_payload', function( $payload, $form_data, $filepath ) {
    if ( isset( $payload['data']['my_plugin']['local_secret'] ) ) {
        unset( $payload['data']['my_plugin']['local_secret'] );
    }

    return $payload;
}, 10, 3 );

Submission admin table hooks

These hooks customize the WordPress admin submission list table.

Hook Type Arguments Use it to
elzo_forms_submission_table_field_labels Filter $field_labels, $submission_fields Modify dynamic field column labels.
elzo_forms_submission_table_field Filter $field, $column, $submission_data Modify the value used for a submission table field column.
elzo_forms_submission_table_form_title Filter $form_title, $form_id Modify the displayed form title in the submissions table.
elzo_forms_submission_table_form_edit_url Filter $form_edit_url, $form_id Modify the form edit URL in the submissions table.
elzo_forms_submission_form_dropdown_options Filter $forms Modify form options in the submission filter dropdown.

Email template hooks

elzo_forms_admin_email_field

Type: Filter
Scope: Free
Runs: while preparing fields for the built-in admin email template.
Use this to: modify how a submitted field appears in the admin notification email.

<?php
add_filter( 'elzo_forms_admin_email_field', function( $field ) {
    if ( isset( $field['value'] ) && is_array( $field['value'] ) ) {
        $field['value'] = implode( ', ', $field['value'] );
    }

    return $field;
} );

File upload hooks

elzo_forms_dangerous_extensions

Type: Filter
Scope: Free
Runs: during upload validation.
Use this to: add more extensions to the blocked file extension list.

Default blocked extensions include: php, php3, php4, php5, phtml, and phar.

<?php
add_filter( 'elzo_forms_dangerous_extensions', function( $extensions ) {
    $extensions[] = 'sh';
    $extensions[] = 'exe';

    return array_values( array_unique( $extensions ) );
} );

Security note: do not remove PHP-related extensions from this list on public websites.


PRO automation hooks

These hooks exist only when Elzo Forms PRO is active. Automations are described in Automations Overview.

elzo_forms_pro_register_automation_extensions

Type: Action

Runs: once, when the automation extension registry boots.

Arguments: $registrar — an \ElzoFormsPro\Automation\Extensions\AutomationExtensionRegistrar.

Use this to: register custom actions, condition operators, controls, codecs, categories, and workflow data providers.

This is the single entry point for extending automations. There is no filter that accepts a list of class names — you register instances (or factories) on the registrar:

<?php
add_action( 'elzo_forms_pro_register_automation_extensions', function ( $registrar ) {
    $registrar->register_action( new \MyPlugin\Automation\CreateLeadAction() );
} );

The registrar exposes:

Method Registers
register_action( ActionInterface $action ) An automation action.
register_action_factory( string $type, callable $factory ) An action constructed on first use.
register_condition_operator( ConditionOperatorInterface $operator ) A condition operator.
register_condition_operator_factory( string $key, callable $factory ) An operator constructed on first use.
register_control( ActionControlInterface $control ) A settings control for the action editor.
register_codec( ActionCodecInterface $codec ) A codec that converts editor input to stored settings.
register_category( AutomationCategoryDefinition $category ) A category in the action library.
register_data_provider( WorkflowDataProviderInterface $provider ) A workflow data provider.

Registries are validated and frozen immediately after this action completes. Registering later throws a LogicException, so hook this action rather than deferring to init.

If registration throws, the whole extension registry fails to boot, elzo_forms_pro_automation_extensions_boot_failed fires with the exception, and no automation runs for that request.

The action contract

An action implements \ElzoFormsPro\Automation\Contracts\ActionInterface, which has exactly two methods:

<?php
namespace MyPlugin\Automation;

use ElzoFormsPro\Automation\Contracts\ActionInterface;
use ElzoFormsPro\Automation\Runtime\ExecutionContext;
use ElzoFormsPro\Automation\ValueObjects\ActionDefinition;
use ElzoFormsPro\Automation\ValueObjects\ActionResult;

class CreateLeadAction implements ActionInterface {
    public function get_definition(): ActionDefinition {
        return new ActionDefinition(
            'my_create_lead',
            __( 'Create lead', 'my-plugin' ),
            __( 'Send the submission to the CRM.', 'my-plugin' ),
            'http',
            'elzo-icon-link',
            [
                'type'       => 'object',
                'properties' => [
                    'list_id' => [
                        'type'          => 'string',
                        'label'         => __( 'List ID', 'my-plugin' ),
                        'allow_dynamic' => true,
                        'ui'            => [ 'control' => 'text' ],
                    ],
                ],
                'required'              => [ 'list_id' ],
                'additional_properties' => false,
            ],
            [
                'type'       => 'object',
                'properties' => [
                    'lead_id' => [ 'type' => 'integer', 'label' => __( 'Lead ID', 'my-plugin' ) ],
                ],
                'required'              => [ 'lead_id' ],
                'additional_properties' => false,
            ],
            [ 'has_side_effects' => true, 'supports_test' => true ]
        );
    }

    public function execute( array $settings, ExecutionContext $context ): ActionResult {
        $lead_id = my_plugin_create_lead( $settings['list_id'] );

        if ( ! $lead_id ) {
            return ActionResult::failed( 'my_create_lead_failed', __( 'The CRM rejected the lead.', 'my-plugin' ) );
        }

        return ActionResult::success( [ 'lead_id' => $lead_id ], __( 'Lead created.', 'my-plugin' ) );
    }
}

ActionDefinition takes the type, label, description, category, icon, settings schema, outputs schema, and flags. Settings are validated against the schema before execute() runs, and outputs are validated against the outputs schema after it returns — an output the schema does not declare is rejected.

Declaring 'supports_test' => true together with 'has_side_effects' => true also requires implementing SimulatableActionInterface, or registration fails validation.

ActionResult

Return one of three factory methods:

Factory Signature
ActionResult::success() $outputs, $message, $context_patch, $response_patch, $control, $meta
ActionResult::failed() $error_code, $error_message, $error_data, $outputs, $control, $meta
ActionResult::skipped() $message, $meta

$control accepts ActionResult::CONTROL_CONTINUE, CONTROL_STOP_AUTOMATION, or CONTROL_STOP_ALL. The action’s configured On error setting applies on failure; returning an explicit control value overrides it.

$context_patch writes values back into workflow state for later actions. $response_patch modifies the response returned to the browser. Both are size-limited by the runtime and validated before they are applied.

ExecutionContext

Actions receive an ExecutionContext object, not an array. Read workflow data through it by path:

$email   = $context->get( 'fields.by_key.email', '' );
$form    = $context->get_form();        // \ElzoForms\Form\Form
$entry   = $context->get_submission();  // \ElzoForms\Submission\Submission
$user    = $context->get_user();        // \WP_User
$is_test = $context->is_test();
Method Returns
get( string $path, $default = null ) A workflow value, such as fields.by_key.email or variables.total.
has( string $path ) Whether the path resolves.
resolve( $value ) A ValueResolutionResult for a value containing {{ }} references.
get_form(), get_submission(), get_user() The form, submission, and submitting user.
is_live(), is_test(), get_mode() Whether this is a real run or a test run.
get_http_client() The guarded HTTP client. Use it instead of wp_remote_request() so runtime limits apply.
get_remaining_deadline_seconds() Time left in the run’s budget.

Check is_test() before performing any side effect that a configuration test should not cause.

Lifecycle actions

Hook Arguments Runs
elzo_forms_pro_automation_before_automation $automation, $context Before an automation’s conditions are evaluated.
elzo_forms_pro_automation_after_automation $automation, $context, $automation_result After the automation finishes.
elzo_forms_pro_automation_before_action $automation, $action_payload, $context Before an action executes.
elzo_forms_pro_automation_after_action $automation, $action_payload, $context, $action_result After an action executes.
elzo_forms_pro_automation_after_run $context, $results, $response_patch, $trigger After every automation for the submission has run.
elzo_forms_pro_automation_before_evaluate_conditions $tree, $state, $scope, $scope_id Before a condition tree is evaluated.
elzo_forms_pro_automation_after_evaluate_conditions $result, $tree, $state, $scope, $scope_id After a condition tree is evaluated.

These are notification hooks. They cannot change the automation or its result.

Throwing from a before_ hook stops the automation and records a diagnostic. Throwing from an after_ hook is caught and recorded, and the run continues. Keep the work inside them small, since they execute during the visitor’s request and count against the runtime deadline.

<?php
add_action( 'elzo_forms_pro_automation_after_action', function ( $automation, $action_payload, $context, $action_result ) {
    if ( $action_result->get_status() !== 'failed' ) {
        return;
    }

    error_log( sprintf(
        'Elzo Forms automation action failed: %s (%s)',
        $action_payload['type'] ?? '',
        $action_result->get_error()['code'] ?? ''
    ) );
}, 10, 4 );

Runtime and security filters

Hook Type Arguments Use it to
elzo_forms_pro_automation_runtime_limits Filter $defaults, $form, $source Adjust runtime limits. Values above the hard ceilings are clamped.
elzo_forms_pro_automation_http_allowed_ports Filter $ports Allow ports other than 80 and 443 for outgoing requests.
elzo_forms_pro_automation_http_allow_cross_origin_redirect Filter $allowed, $source_origin, $target_origin, $source Permit a redirect that leaves the original origin. Defaults to false.
elzo_forms_pro_automation_trace_sensitive_keys Filter $additional_keys, $default_keys Add keys that must be redacted from execution traces.
elzo_forms_pro_automation_diagnostic_catalog_entries Filter $entries Add titles, messages, and recommendations for custom error codes.

See Webhook Integrations for Developers for the values these limits take.


Advanced admin hooks

These hooks are mostly useful for admin integrations, custom dashboards, or site-specific tooling.

Hook Type Use it to
elzo_forms_settings_sanitization_rules Filter Modify sanitization rules for an option.
elzo_forms_settings_sanitization_rules_{$option_name} Filter Modify sanitization rules for one specific option.
elzo_forms_settings_default_sanitization_rule Filter Change the fallback sanitization rule.
elzo_forms_get_update_metadata Filter Provide update metadata in custom update flows.
elzo_forms_logic_label_max_length Filter Change the maximum displayed logic label length in the admin UI.
elzo_forms_custom_form_label Filter Display a label for custom or non-numeric form references.
elzo_forms_custom_form_edit_url Filter Display an edit URL for custom or non-numeric form references.
elzo_forms_admin_submission_field Filter Modify fields shown in the admin submission view.
elzo_forms_admin_field Filter Modify admin field data before rendering in the builder.
elzo_forms_admin_field_tabs Filter Add or modify field settings tabs in the admin UI.
elzo_forms_alert_types Filter Add or modify available alert types.

Security guidelines

When using Elzo Forms hooks, follow standard WordPress security practices:

  • Sanitize all data coming from $_POST, $_GET, $_REQUEST, cookies, headers, and external APIs.
  • Escape all output with the correct escaping function, such as esc_html(), esc_attr(), or esc_url().
  • Use current_user_can() before changing admin-only data or privileged settings.
  • Do not store API keys, passwords, access tokens, or secrets inside public form data.
  • Do not remove dangerous upload extensions unless you fully control the upload environment.
  • Use WP_Error for validation failures that should be shown to the user.
  • Keep custom automation logs free of sensitive data.

Choosing the right hook

Goal Recommended hook
Block a submission with a custom error message elzo_forms_validate_submission
Mark a submission as spam elzo_forms_detect_spam
Run logic after a valid submission is saved elzo_forms_after_submission_saved
Change notification recipients dynamically elzo_forms_submission_email_settings
Change the frontend success response elzo_forms_submission_response
Add a custom field type elzo_forms_field_types
Override template arguments elzo_forms/templates/template_args
Register a custom module elzo_forms/modules/register
Register a custom PRO automation action or condition operator elzo_forms_pro_register_automation_extensions

Next steps