JavaScript Customization Guide
Form Plant can also be extended flexibly on the client side ( JavaScript ).
- Custom validation: Add your own per-field validation through a callback API
- Lifecycle events: Hook custom logic into each step of the form with custom events
JavaScript validation exists purely to improve UX ( instant feedback ). Validation that involves security ( restricting to a company domain, integrating with an external API, etc. ) must always also be implemented with the Validation Hooks ( PHP ), since JavaScript can easily be bypassed with DevTools and similar tools.
Supported Scope
| Embed method | Custom validation API | Lifecycle events |
|---|---|---|
| Page ( shortcode ) | Supported | Supported |
| iframe embed | Not supported | Supported ( with some limitations ) |
| JavaScript embed | Not supported | Supported ( with some limitations ) |
Forms placed directly on a page support all features. With iframe / JavaScript embeds, the fplant:validateField event and fplant.addValidator() are not available. See Limitations at the end of this page for details.
How to Load Custom JS
Load your custom JS file with wp_enqueue_script(), specifying fplant-form as a dependency.
// functions.php or a plugin file
function my_fplant_custom_scripts() {
wp_enqueue_script(
'my-fplant-custom',
get_stylesheet_directory_uri() . '/js/my-fplant-custom.js',
array( 'fplant-form' ), // load after form.js
'1.0.0',
true
);
}
add_action( 'wp_enqueue_scripts', 'my_fplant_custom_scripts' );
By specifying fplant-form as a dependency, your custom JS runs only when the window.fplant API is available.
Custom Validation API
fplant.addValidator(fieldName, callback)
Registers a custom validator for a field.
window.fplant.addValidator('email', function (value, fieldName, formData) {
// value: current value of the field
// fieldName: field name (e.g. 'email')
// formData: data object containing all fields
if (value && !value.endsWith('@example.com')) {
return 'Only the @example.com domain is allowed'; // return an error message
}
return ''; // an empty string means validation passed
});
Arguments:
| Argument | Type | Description |
|---|---|---|
fieldName | string | The name attribute value of the target field |
callback | function | The validation function |
Callback function:
| Argument | Type | Description |
|---|---|---|
value | string / Array / File / null | Current value of the field ( see below ) |
fieldName | string | Field name |
formData | object | Data for the whole form ( { fieldName: value, ... } ) |
Type of value by field type:
| Field type | Type of value | Contents |
|---|---|---|
| Text / Email / URL, etc. | string | Input value |
| Textarea | string | Input value |
| Checkbox | string[] | Array of checked values |
| Radio button | string / null | Selected value, or null when nothing is selected |
| File | File / null | Selected file object, or null when no file is selected |
Return value:
- Returning an error message string makes the field invalid
- Returning an empty string or a falsy value (
'',null,undefined) means validation passed
fplant.removeValidator(fieldName, callback)
Removes a registered validator.
// Remove a specific validator
var myValidator = function (value) { /* ... */ };
fplant.addValidator('email', myValidator);
fplant.removeValidator('email', myValidator);
// Remove all validators for a field ( omit callback )
fplant.removeValidator('email');
Example: Cross-Field Validation
// Check that the password confirmation matches
fplant.addValidator('password_confirm', function (value, fieldName, formData) {
if (value && formData.password && value !== formData.password) {
return 'The passwords do not match';
}
return '';
});
Example: Conditionally Required Check
// Require the detail field only when "Other" is selected
fplant.addValidator('other_detail', function (value, fieldName, formData) {
if (formData.category === 'other' && (!value || value.trim() === '')) {
return 'Please enter details when "Other" is selected';
}
return '';
});
Example: Character Limit
fplant.addValidator('message', function (value) {
if (value && value.length > 500) {
return 'The message must be 500 characters or fewer ( currently: ' + value.length + ' characters )';
}
return '';
});
Lifecycle Events Overview
A CustomEvent fires at each step of the form. Register listeners on the form element ( .fplant-form ).
Form initialization ──── fplant:init
│
Input / validation ───── fplant:validateField (cancelable)
│
Submit button click
│
Validation
├── on error ──── fplant:error
│
Before submit ───────── fplant:beforeSubmit (cancelable)
│
Loading ─────────────── fplant:loading { loading: true }
│
Confirmation shown ──── fplant:confirmationShow
│ ├── go back ──── fplant:confirmationHide
│
Final submit
│
Loading ─────────────── fplant:loading { loading: false }
│
├── success ──────── fplant:success
└── error ────────── fplant:submitError
Event List
| Event name | cancelable | When it fires |
|---|---|---|
fplant:init | false | When form initialization completes |
fplant:validateField | true | After each field is validated ( only when there is no error ) |
fplant:beforeSubmit | true | After validation succeeds, just before the submit processing |
fplant:error | false | When a validation error is displayed |
fplant:loading | false | When the loading state changes |
fplant:confirmationShow | false | When the confirmation screen is shown |
fplant:confirmationHide | false | When returning from the confirmation screen to the input screen |
fplant:success | false | When the form is submitted successfully |
fplant:submitError | false | On an error after the server submission |
About cancelable events: For events with cancelable: true, you can interrupt the processing by calling e.preventDefault().
Details and Examples for Each Event
fplant:init
Fires when the form finishes initializing.
| Property ( detail ) | Type | Description |
|---|---|---|
formId | string | Form ID |
form | HTMLElement | Form element |
document.addEventListener('fplant:init', function (e) {
console.log('Form #' + e.detail.formId + ' has been initialized');
var form = e.detail.form;
form.querySelector('.my-custom-field')?.classList.add('initialized');
});
fplant:validateField
Fires after each field is validated ( only when neither the built-in validation nor the custom validators produced an error ). Because it is cancelable: true, you can call preventDefault() to display your own error.
| Property ( detail ) | Type | Description |
|---|---|---|
fieldName | string | Field name |
value | any | Current value of the field |
field | HTMLElement | Field element |
group | HTMLElement | Field group element |
errorMessage | string / null | Error message ( settable from the listener ) |
Displaying a custom error with preventDefault():
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:validateField', function (e) {
if (e.detail.fieldName === 'email') {
var value = e.detail.value;
if (value && value.indexOf('@company.co.jp') === -1) {
e.detail.errorMessage = 'Please use your company email address (@company.co.jp)';
e.preventDefault(); // mark as a validation error
}
}
});
Set the error message on e.detail.errorMessage and call e.preventDefault(). Both are required. If you do not set errorMessage, the default error message is shown.
Choosing between this and addValidator():
addValidator()— best for simple validation; you just pass a callback functionfplant:validateField— convenient when you need access to DOM elements, or when the decision depends on a combination of multiple fields
fplant:beforeSubmit
Fires after validation succeeds, just before the actual submit processing. Because it is cancelable: true, you can call preventDefault() to abort the submission.
| Property ( detail ) | Type | Description |
|---|---|---|
formId | string | Form ID |
formData | object | The form data to be submitted |
// Track the start of a form submission in GA4
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:beforeSubmit', function (e) {
gtag('event', 'form_submit_start', {
form_id: e.detail.formId,
});
});
// Show a confirmation dialog before submitting
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:beforeSubmit', function (e) {
if (!confirm('Are you sure you want to submit this content ?')) {
e.preventDefault(); // abort the submission
}
});
fplant:error
Fires when a validation error is displayed.
| Property ( detail ) | Type | Description |
|---|---|---|
fieldErrors | object | Per-field error messages ( { fieldName: 'error text', ... } ) |
message | string | The overall error message |
// Shake animation on error
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:error', function (e) {
Object.keys(e.detail.fieldErrors).forEach(function (fieldName) {
var group = document.querySelector(
'.fplant-field-group[data-field-name="' + fieldName + '"]'
);
if (group) {
group.classList.add('shake');
setTimeout(function () { group.classList.remove('shake'); }, 600);
}
});
});
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-5px); }
40%, 80% { transform: translateX(5px); }
}
.shake {
animation: shake 0.6s ease;
}
fplant:loading
Fires when the loading state changes. It fires twice: when the submission starts ( true ) and when it completes ( false ).
| Property ( detail ) | Type | Description |
|---|---|---|
loading | boolean | true: loading started, false: loading finished |
// Custom loading overlay
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:loading', function (e) {
var overlay = document.getElementById('my-loading-overlay');
if (overlay) {
overlay.style.display = e.detail.loading ? 'flex' : 'none';
}
});
fplant:confirmationShow
Fires when the confirmation screen is shown.
| Property ( detail ) | Type | Description |
|---|---|---|
formId | string | Form ID |
confirmationEl | HTMLElement | The confirmation screen element |
// Fade-in animation for the confirmation screen
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:confirmationShow', function (e) {
var confirmation = e.detail.confirmationEl;
confirmation.style.opacity = '0';
confirmation.style.transition = 'opacity 0.3s ease';
requestAnimationFrame(function () {
confirmation.style.opacity = '1';
});
});
// Scroll to the top of the page when the confirmation screen is shown
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:confirmationShow', function (e) {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
fplant:confirmationHide
Fires when returning from the confirmation screen to the input screen.
| Property ( detail ) | Type | Description |
|---|---|---|
formId | string | Form ID |
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:confirmationHide', function (e) {
form.style.opacity = '0';
form.style.transition = 'opacity 0.3s ease';
requestAnimationFrame(function () {
form.style.opacity = '1';
});
});
fplant:success
Fires when the form is submitted successfully. The response data from the server is included directly in detail.
| Property ( detail ) | Type | Description |
|---|---|---|
action_type | string | Post-submission action ( 'message' / 'redirect' / 'custom_page' ) |
message | string | Success message |
redirect_url | string | Redirect destination URL ( when action_type is 'redirect' ) |
success_page_html | string | Custom completion screen HTML ( when action_type is 'custom_page' ) |
// Conversion tracking in GA4
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:success', function (e) {
gtag('event', 'form_submit_success', {
action_type: e.detail.action_type,
});
});
// Run custom logic after a successful submission
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:success', function (e) {
setTimeout(function () {
window.location.href = '/thank-you/';
}, 3000);
});
fplant:submitError
Fires when an error is returned after submitting to the server. Unlike a validation error ( fplant:error ), this is an error that occurs after communicating with the server.
| Property ( detail ) | Type | Description |
|---|---|---|
message | string | Error message |
errors | object | Per-field errors ( { fieldName: 'error text', ... } ) |
var form = document.querySelector('.fplant-form');
form.addEventListener('fplant:submitError', function (e) {
console.error('Form submission error:', e.detail.message);
// Send to Sentry or similar
if (typeof Sentry !== 'undefined') {
Sentry.captureMessage('Form submission error: ' + e.detail.message);
}
});
Notes
JS Validation Is Not a Security Measure
JavaScript validation is intended to improve UX ( instant feedback ). Perform security-related input validation with the Validation Hooks ( PHP ). Because JavaScript can easily be bypassed with DevTools and similar tools, server-side validation always runs independently.
Error Messages Are Handled Safely
Error messages set by custom validators or fplant:validateField are displayed via textContent. HTML tags are not interpreted and are shown as plain text, so there is no XSS risk.
Error Handling in Validators
If an exception is thrown inside a callback function registered with addValidator(), the error is automatically ignored and does not affect the form's behavior. For easier debugging during development, we recommend handling errors appropriately within your callbacks.
When to Register Event Listeners
Except for fplant:init, event listeners can be registered at any time as long as the form's DOM exists. Registering them after DOMContentLoaded is the safe approach.
document.addEventListener('DOMContentLoaded', function () {
var form = document.querySelector('.fplant-form');
if (form) {
form.addEventListener('fplant:beforeSubmit', function (e) {
// ...
});
}
});
When There Are Multiple Forms
When there are multiple forms on the same page, either register a listener on each form element individually, or use e.detail.formId to identify the target form. Because events fire with bubbles: true, you can also listen at the document level.
document.addEventListener('fplant:beforeSubmit', function (e) {
if (e.detail.formId === '145') {
// process only for form #145
}
});
iframe / JavaScript Embed Limitations
With iframe embeds and JavaScript embeds ( embed.js ), the following limitations apply.
- The
fplant:validateFieldevent does not fire ( there is no client-side validation ) window.fplant.addValidator()/removeValidator()are not available- The
detail.formDataoffplant:beforeSubmitis not included ( onlyformId) - The
detail.confirmationEloffplant:confirmationShowis not included ( onlyformId)