JavaScript Hooks and Events API

Applies to: Elzo Forms Free and Elzo Forms Pro frontend forms.

Audience: Developers extending Elzo Forms on the frontend with WordPress JavaScript actions and filters.

Elzo Forms frontend extensibility is based on the WordPress wp.hooks package. Use these JavaScript hooks when you need to validate forms in the browser, prepare data before submission, integrate analytics, add anti-spam tokens, customize modal or alert behavior, react to multi-step navigation, or extend file upload behavior without editing Elzo Forms core JavaScript files.

Important: this API uses WordPress JavaScript hooks, not the old ElzoFormsEvents, EventTarget, or CustomEvent API. Callbacks receive regular function arguments, not a browser event object.


How to load your script

Enqueue your integration script after the Elzo Forms frontend script handle elzo-forms-script. The Elzo Forms script depends on WordPress wp-hooks, so your script can use wp.hooks when it is loaded after elzo-forms-script.

<?php
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_script(
        'my-elzo-forms-integration',
        plugin_dir_url( __FILE__ ) . 'assets/js/my-elzo-forms-integration.js',
        array( 'elzo-forms-script' ),
        '1.0.0',
        true
    );
} );

Inside your JavaScript file, register callbacks with wp.hooks.addAction() and wp.hooks.addFilter().

wp.hooks.addAction(
    'elzoForms.form.success',
    'my-plugin/track-success',
    function(form, response, data, formData, submitContext) {
        console.log('Elzo Forms submitted successfully', form, data);
    }
);

Core concepts

Actions vs filters

Actions let you react to something that happened. They do not control the return value of the workflow.

wp.hooks.addAction(
    'elzoForms.form.success',
    'my-plugin/example',
    function(form, response, data, formData, submitContext) {
        // React to a successful submission.
    }
);

Filters let you modify a value or allow/block a step. A filter must return the value that should be used next.

wp.hooks.addFilter(
    'elzoForms.form.submit.shouldSubmit',
    'my-plugin/confirm-submit',
    function(shouldSubmit, form, submitContext) {
        return window.confirm('Submit this form?');
    }
);

Important: actions are not cancelable with event.preventDefault(). To block something, use the matching filter, such as elzoForms.form.submit.shouldSubmit, elzoForms.form.submit.shouldSend, elzoForms.modal.shouldOpen, elzoForms.alert.shouldShow, elzoForms.step.change.shouldChange, or elzoForms.file.upload.shouldUpload.

Callback namespaces

Every WordPress JavaScript hook callback needs a unique namespace. Use a stable namespace such as vendor/plugin/feature.

wp.hooks.addAction(
    'elzoForms.form.submit.before',
    'acme-crm/elzo-forms/log-submit-start',
    function(form, submitContext) {
        // Your logic here.
    }
);

Context objects

Most Elzo Forms JavaScript hooks pass a context object as the last argument. Context objects provide workflow details without forcing every hook to have a long, fragile list of positional arguments.

Context objects are mutable. You may attach your own integration data to a context object when you need to read it later in the same workflow. However, when using a filter, always return the filtered value. Do not rely only on mutating the context object to change the main result.


Submission lifecycle

Most frontend submission hooks run in this order:

  1. The browser submit event is captured.
  2. elzoForms.form.submit.before runs.
  3. elzoForms.form.submit.shouldSubmit decides whether the submission should continue.
  4. Frontend validation runs.
  5. elzoForms.form.submit.validationFailed runs if frontend validation fails.
  6. The form enters the loading state.
  7. elzoForms.form.submit.beforeSend runs for async preparation, such as reCAPTCHA or external tokens.
  8. Elzo Forms builds FormData.
  9. elzoForms.form.submit.formData can modify the FormData object.
  10. elzoForms.form.submit.shouldSend decides whether the AJAX request should be sent.
  11. elzoForms.form.submit runs immediately before the request.
  12. elzoForms.form.ajax.start runs before fetch().
  13. elzoForms.form.ajax.end runs after the AJAX response is parsed.
  14. elzoForms.form.error runs for a valid AJAX response with success: false.
  15. elzoForms.form.success runs for a valid AJAX response with success: true.
  16. elzoForms.form.submit.error runs when JavaScript preparation, network handling, or response parsing throws an error.

Important: use the earliest hook that matches your goal. For example, use shouldSubmit to block before validation, beforeSend to add async tokens before FormData is built, formData to add fields to the request, and form.success to react after the server accepts the submission.


Async work before submission

Use elzoForms.form.submit.beforeSend when your integration must finish before Elzo Forms builds FormData and sends the AJAX request.

The hook receives (form, beforeSendPromises, submitContext). Push a Promise into beforeSendPromises. Elzo Forms waits for all promises before continuing.

wp.hooks.addAction(
    'elzoForms.form.submit.beforeSend',
    'my-plugin/load-token',
    function(form, beforeSendPromises, submitContext) {
        const promise = fetch('/my-token-endpoint')
            .then(function(response) {
                return response.json();
            })
            .then(function(data) {
                let input = form.querySelector('input[name="my_token"]');

                if (!input) {
                    input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = 'my_token';
                    form.appendChild(input);
                }

                input.value = data.token;
                submitContext.myTokenLoaded = true;
            });

        beforeSendPromises.push(promise);

        return promise;
    }
);

Why push the Promise? Elzo Forms supports WordPress 6.5 and newer. Pushing into beforeSendPromises is the compatibility-safe way to make sure async work is awaited before submission continues.


Quick reference

Hook Type Use it to
elzoForms.form.submit.before Action Run logic as soon as an Elzo form submit is captured.
elzoForms.form.submit.shouldSubmit Filter Allow or block submission before frontend validation.
elzoForms.form.submit.validationFailed Action React when frontend validation blocks submission.
elzoForms.form.submit.beforeSend Action Run async preparation before FormData is built.
elzoForms.form.submit.formData Filter Add, remove, or modify submitted FormData.
elzoForms.form.submit.shouldSend Filter Allow or block the AJAX request after FormData exists.
elzoForms.form.submit Action React immediately before the AJAX request is sent.
elzoForms.form.submit.cancelled Action React when a submit filter cancels submission.
elzoForms.form.submit.error Action React to JavaScript, async preparation, network, or parsing errors.
elzoForms.form.ajax.start Action React when the AJAX request starts.
elzoForms.form.ajax.end Action Inspect the parsed AJAX response.
elzoForms.form.ajax.error Action React to AJAX-related errors.
elzoForms.form.error Action React to a server response with success: false.
elzoForms.form.success Action React to a successful server response.
elzoForms.validation.step.start Action Run logic before a step is validated.
elzoForms.validation.step.isValid Filter Modify the validation result for one step.
elzoForms.validation.step.validated Action React after a step validation result is known.
elzoForms.validation.form.start Action Run logic before the whole form is validated.
elzoForms.validation.form.isValid Filter Modify the final frontend validation result.
elzoForms.validation.form.validated Action React after the final frontend validation result is known.
elzoForms.step.change.shouldChange Filter Allow or block a multi-step navigation change.
elzoForms.step.change.before Action React before the active step changes.
elzoForms.step.change.after Action React after the active step changes.
elzoForms.modal.args Filter Modify modal message or button text before opening.
elzoForms.modal.shouldOpen Filter Allow or block a modal before it opens.
elzoForms.modal.open Action React before modal DOM is created.
elzoForms.modal.opened Action React after a modal is visible.
elzoForms.modal.close Action React when one modal starts closing.
elzoForms.modal.closed Action React after one modal has been removed.
elzoForms.modal.closeAll Action React when all open modals start closing.
elzoForms.modal.closedAll Action React after all open modals have been closed.
elzoForms.alert.args Filter Modify alert message, container, or type before showing.
elzoForms.alert.shouldShow Filter Allow or block an inline alert before it is shown.
elzoForms.alert.show Action React before alert DOM is created.
elzoForms.alert.shown Action React after an alert has been inserted.
elzoForms.alert.close Action React before an alert is removed.
elzoForms.alert.closed Action React after an alert has been removed.
elzoForms.file.upload.shouldUpload Filter Allow or block a selected file before upload begins.
elzoForms.file.upload.formData Filter Modify file upload FormData.
elzoForms.file.upload.before Action React before a file upload request starts.
elzoForms.file.upload.progress Action Track upload progress.
elzoForms.file.upload.success Action React after a file upload succeeds.
elzoForms.file.upload.error Action React after a file upload fails.
elzoForms.file.upload.cancelled Action React when upload is cancelled by shouldUpload.
elzoForms.file.upload.rejected Action React when Elzo Forms rejects a file before upload, such as file size or max file count.

Submission hooks

elzoForms.form.submit.before

Type: Action
Runs: immediately after an Elzo Forms submit event is captured and before frontend validation.
Signature:

wp.hooks.addAction(
    'elzoForms.form.submit.before',
    'my-plugin/example',
    function(form, submitContext) {
        // Your logic here.
    }
);

elzoForms.form.submit.shouldSubmit

Type: Filter
Runs: before frontend validation.
Return: boolean. Return false to stop the submission.

wp.hooks.addFilter(
    'elzoForms.form.submit.shouldSubmit',
    'my-plugin/confirm-submit',
    function(shouldSubmit, form, submitContext) {
        if (!window.confirm('Submit this form?')) {
            return false;
        }

        return shouldSubmit;
    }
);

elzoForms.form.submit.beforeSend

Type: Action
Runs: after frontend validation succeeds and before Elzo Forms builds FormData.
Use this to: add hidden fields, run reCAPTCHA, request external tokens, or finish async preparation.

wp.hooks.addAction(
    'elzoForms.form.submit.beforeSend',
    'my-plugin/prepare',
    function(form, beforeSendPromises, submitContext) {
        const promise = Promise.resolve().then(function() {
            let input = form.querySelector('input[name="prepared_by"]');

            if (!input) {
                input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'prepared_by';
                form.appendChild(input);
            }

            input.value = 'my-plugin';
        });

        beforeSendPromises.push(promise);
    }
);

elzoForms.form.submit.formData

Type: Filter
Runs: after FormData is built and before the AJAX request is sent.
Return: a FormData object.

wp.hooks.addFilter(
    'elzoForms.form.submit.formData',
    'my-plugin/add-form-data',
    function(formData, form, submitContext) {
        formData.set('source_page', window.location.href);

        return formData;
    }
);

elzoForms.form.submit.shouldSend

Type: Filter
Runs: after FormData exists and before the AJAX request is sent.
Return: boolean. Return false to stop the AJAX request.

wp.hooks.addFilter(
    'elzoForms.form.submit.shouldSend',
    'my-plugin/block-debug-submissions',
    function(shouldSend, form, formData, submitContext) {
        if (formData.get('debug_block') === 'yes') {
            return false;
        }

        return shouldSend;
    }
);

elzoForms.form.success

Type: Action
Runs: after the server returns a successful Elzo Forms response.
Use this to: track conversions, update UI, send analytics events, or react to redirect data.

wp.hooks.addAction(
    'elzoForms.form.success',
    'my-plugin/analytics',
    function(form, response, data, formData, submitContext) {
        window.dataLayer = window.dataLayer || [];

        window.dataLayer.push({
            event: 'elzo_form_success',
            formAction: submitContext.formAction,
            message: submitContext.message || ''
        });
    }
);

elzoForms.form.error

Type: Action
Runs: when the server response is valid but contains success: false.
Use this to: track server-side validation errors or display additional UI.

wp.hooks.addAction(
    'elzoForms.form.error',
    'my-plugin/log-server-error',
    function(form, response, message, data, formData, submitContext) {
        console.warn('Elzo Forms server-side error:', message, data);
    }
);

elzoForms.form.submit.error

Type: Action
Runs: when frontend preparation, network handling, or response parsing throws an error.
Use this to: log technical errors that are not normal server-side validation responses.

wp.hooks.addAction(
    'elzoForms.form.submit.error',
    'my-plugin/log-submit-error',
    function(form, error, formData, submitContext) {
        console.error('Elzo Forms submit failed during phase:', submitContext.failedPhase, error);
    }
);

Validation hooks

elzoForms.validation.step.isValid

Type: Filter
Runs: after Elzo Forms validates one step.
Return: boolean. Return false to mark the step invalid.

wp.hooks.addFilter(
    'elzoForms.validation.step.isValid',
    'my-plugin/validate-step',
    function(isValid, step, form, context) {
        const customField = step.querySelector('[name="custom_code"]');

        if (customField && customField.value === 'blocked') {
            customField.classList.add('invalid');
            context.invalidFields.push(customField);

            return false;
        }

        return isValid;
    }
);

elzoForms.validation.form.isValid

Type: Filter
Runs: after all form steps are validated.
Return: boolean. Return false to block the form before submission.

wp.hooks.addFilter(
    'elzoForms.validation.form.isValid',
    'my-plugin/validate-form',
    function(isValid, form, context) {
        const email = form.querySelector('input[type="email"]');

        if (email && email.value.endsWith('@example.test')) {
            email.classList.add('invalid');

            return false;
        }

        return isValid;
    }
);

The validation context may include:

  • settings — frontend form settings.
  • errorClass — CSS classes used for invalid fields.
  • inputs — fields inside the current step.
  • checkboxGroups — required checkbox groups inside the current step.
  • fileDropAreas — file upload areas inside the current step.
  • invalidFields — fields detected as invalid.
  • invalidGroups — checkbox groups detected as invalid.
  • invalidFileDropAreas — file upload areas detected as invalid.
  • invalidSteps — form-level list of invalid steps.

Multi-step hooks

Use step change hooks to react to or control multi-step navigation.

wp.hooks.addFilter(
    'elzoForms.step.change.shouldChange',
    'my-plugin/prevent-step-change',
    function(shouldChange, form, currentStep, nextStep, direction, context) {
        if (direction === 'next' && form.classList.contains('waiting-for-external-check')) {
            return false;
        }

        return shouldChange;
    }
);
wp.hooks.addAction(
    'elzoForms.step.change.after',
    'my-plugin/track-step-change',
    function(form, currentStep, nextStep, direction, context) {
        console.log('Step changed:', direction, context.currentIndex, context.nextIndex);
    }
);

The step change context may include:

  • settings — frontend form settings.
  • trigger — button or element that triggered the change, when available.
  • steps — all step elements.
  • currentIndex — previous step index.
  • nextIndex — next step index.

Modal hooks use a single-modal lifecycle. elzoForms.modal.close and elzoForms.modal.closed receive one modal, its paired backdrop, and a context object. Bulk closing is exposed separately through elzoForms.modal.closeAll and elzoForms.modal.closedAll.

Customize modal content

wp.hooks.addFilter(
    'elzoForms.modal.args',
    'my-plugin/customize-modal',
    function(modalArgs, context) {
        if (context.reason === 'submissionSuccess') {
            modalArgs.button = 'Great';
        }

        return modalArgs;
    }
);

Prevent a modal

wp.hooks.addFilter(
    'elzoForms.modal.shouldOpen',
    'my-plugin/prevent-modal',
    function(shouldOpen, message, button, context) {
        if (context.reason === 'fileUploadRejected') {
            return false;
        }

        return shouldOpen;
    }
);

React to modal close

wp.hooks.addAction(
    'elzoForms.modal.closed',
    'my-plugin/modal-closed',
    function(modal, backdrop, context) {
        console.log('Modal closed because:', context.reason);
    }
);

Common modal context fields:

  • modalId — generated modal instance ID.
  • reason — why the modal opened or closed, such as programmatic, button, backdrop, escape, replace, validation, submissionSuccess, submissionError, fileUploadRejected, or fileUploadError.
  • trigger — element that triggered the action, when available.
  • modal — modal element.
  • backdrop — paired backdrop element.
  • restoreFocus — whether Elzo Forms should restore focus after closing.

Note: in elzoForms.modal.closed, the modal and backdrop elements have already been removed from the DOM, but they are still passed to the callback for inspection.


Alert hooks

Alert hooks control inline alert messages. Use them when the form is configured to show messages inside the form instead of a modal.

wp.hooks.addFilter(
    'elzoForms.alert.args',
    'my-plugin/customize-alert',
    function(alertArgs, context) {
        if (alertArgs.type === 'error') {
            alertArgs.message = '<strong>Please check the form.</strong> ' + alertArgs.message;
        }

        return alertArgs;
    }
);
wp.hooks.addAction(
    'elzoForms.alert.shown',
    'my-plugin/alert-shown',
    function(alert, message, container, type, context) {
        console.log('Alert shown:', type, context.reason);
    }
);

Common alert context fields:

  • reason — why the alert is shown or closed.
  • trigger — element that triggered the action, when available.
  • container — alert container.
  • type — alert type, usually error or success.
  • alert — alert element after it is created.

File upload hooks

File upload hooks let you control file selection, enrich upload requests, track progress, and react to upload results.

Block a file before upload

wp.hooks.addFilter(
    'elzoForms.file.upload.shouldUpload',
    'my-plugin/block-file',
    function(shouldUpload, file, dropArea, uploadContext) {
        if (file.type === 'application/x-msdownload') {
            return false;
        }

        return shouldUpload;
    }
);

Add data to the upload request

wp.hooks.addFilter(
    'elzoForms.file.upload.formData',
    'my-plugin/file-upload-form-data',
    function(formData, file, dropArea, uploadContext) {
        formData.set('source_url', window.location.href);

        return formData;
    }
);

Track upload progress

wp.hooks.addAction(
    'elzoForms.file.upload.progress',
    'my-plugin/file-progress',
    function(file, dropArea, progress, uploadContext) {
        console.log('Upload progress for', file.name, progress + '%');
    }
);

React to file upload result

wp.hooks.addAction(
    'elzoForms.file.upload.success',
    'my-plugin/file-success',
    function(file, dropArea, response, uploadContext) {
        console.log('Uploaded file URL:', uploadContext.fileUrl);
    }
);

wp.hooks.addAction(
    'elzoForms.file.upload.error',
    'my-plugin/file-error',
    function(file, dropArea, errorOrResponse, uploadContext) {
        console.error('File upload failed:', file.name, errorOrResponse);
    }
);

Common upload context fields:

  • form — parent form element.
  • dropArea — file upload drop area.
  • fileInput — original file input element.
  • settings — frontend form settings.
  • chunkSize — chunk size used by Elzo Forms.
  • maxFileSize — maximum allowed file size in bytes.
  • url — AJAX upload URL.
  • item — upload list item element.
  • formData — upload request FormData.
  • progress — upload progress from 0 to 100.
  • response — latest server response.
  • error — upload error when available.
  • fileUrl — uploaded file URL after success.

Context reference

submitContext

Submission hooks receive submitContext as the last argument.

  • event — original browser submit event.
  • settings — frontend form settings.
  • currentStep — active step when the form was submitted.
  • stepAlertWrapper — step-level alert wrapper when available.
  • alertContainer — container used for submission messages.
  • formAction — form action URL.
  • formMethod — form method, usually POST.
  • submitButtons — submit buttons affected by loading state.
  • formData — submitted FormData after it is built.
  • fetchResponse — raw fetch() response after AJAX completes.
  • response — parsed JSON response.
  • data — normalized response data object.
  • message — normalized response message.
  • phase — current submission phase.
  • failedPhase — phase where an exception occurred.
  • error — exception object for submit errors.

Typical phase values include before, validation, validationFailed, beforeSend, formData, submit, ajax, success, error, and cancelled.


Migration from the old JavaScript events API

Older examples may refer to ElzoFormsEvents.on(), event.detail, and event names like form:success. Replace those with WordPress JavaScript hooks.

Old API New API
ElzoFormsEvents.on('form:success', callback) wp.hooks.addAction('elzoForms.form.success', namespace, callback)
event.detail.form form positional argument
event.detail.response response positional argument
event.detail.data data positional argument
event.preventDefault() Return false from a matching filter.
Mutable event.detail filter-like events Use wp.hooks.addFilter() and return the filtered value.

Old form-specific events such as form:success:123 are no longer needed. Check the current form inside your callback instead.

wp.hooks.addAction(
    'elzoForms.form.success',
    'my-plugin/form-123-success',
    function(form, response, data, formData, submitContext) {
        const formIdInput = form.querySelector('input[name="elzo_form_id"]');
        const formId = formIdInput ? formIdInput.value : '';

        if (formId !== '123') {
            return;
        }

        console.log('Form 123 submitted successfully.');
    }
);

Best practices

  • Enqueue your script with elzo-forms-script as a dependency.
  • Use unique callback namespaces, such as vendor/plugin/feature.
  • Use actions to react and filters to change decisions or values.
  • Always return a value from filters.
  • Use beforeSendPromises for async work that must finish before submission.
  • Prefer submitContext, uploadContext, and validation context data over manually searching the DOM when the context already contains what you need.
  • Do not edit Elzo Forms core JavaScript files for integrations.
  • Keep frontend validation as a user experience improvement only. Security-critical validation must also run on the server.
  • Do not assume every form has the same fields, steps, or upload areas. Check that elements exist before using them.
  • When modifying messages that include HTML, make sure any dynamic data is properly escaped before inserting it.