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:
- The browser submit event is captured.
elzoForms.form.submit.beforeruns.elzoForms.form.submit.shouldSubmitdecides whether the submission should continue.- Frontend validation runs.
elzoForms.form.submit.validationFailedruns if frontend validation fails.- The form enters the loading state.
elzoForms.form.submit.beforeSendruns for async preparation, such as reCAPTCHA or external tokens.- Elzo Forms builds
FormData. elzoForms.form.submit.formDatacan modify theFormDataobject.elzoForms.form.submit.shouldSenddecides whether the AJAX request should be sent.elzoForms.form.submitruns immediately before the request.elzoForms.form.ajax.startruns beforefetch().elzoForms.form.ajax.endruns after the AJAX response is parsed.elzoForms.form.errorruns for a valid AJAX response withsuccess: false.elzoForms.form.successruns for a valid AJAX response withsuccess: true.elzoForms.form.submit.errorruns 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
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 asprogrammatic,button,backdrop,escape,replace,validation,submissionSuccess,submissionError,fileUploadRejected, orfileUploadError.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, usuallyerrororsuccess.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 requestFormData.progress— upload progress from0to100.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, usuallyPOST.submitButtons— submit buttons affected by loading state.formData— submittedFormDataafter it is built.fetchResponse— rawfetch()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-scriptas 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
beforeSendPromisesfor 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.