Skip to main content

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
Choosing between client-side and server-side

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 methodCustom validation APILifecycle events
Page ( shortcode )SupportedSupported
iframe embedNot supportedSupported ( with some limitations )
JavaScript embedNot supportedSupported ( 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:

ArgumentTypeDescription
fieldNamestringThe name attribute value of the target field
callbackfunctionThe validation function

Callback function:

ArgumentTypeDescription
valuestring / Array / File / nullCurrent value of the field ( see below )
fieldNamestringField name
formDataobjectData for the whole form ( { fieldName: value, ... } )

Type of value by field type:

Field typeType of valueContents
Text / Email / URL, etc.stringInput value
TextareastringInput value
Checkboxstring[]Array of checked values
Radio buttonstring / nullSelected value, or null when nothing is selected
FileFile / nullSelected 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 namecancelableWhen it fires
fplant:initfalseWhen form initialization completes
fplant:validateFieldtrueAfter each field is validated ( only when there is no error )
fplant:beforeSubmittrueAfter validation succeeds, just before the submit processing
fplant:errorfalseWhen a validation error is displayed
fplant:loadingfalseWhen the loading state changes
fplant:confirmationShowfalseWhen the confirmation screen is shown
fplant:confirmationHidefalseWhen returning from the confirmation screen to the input screen
fplant:successfalseWhen the form is submitted successfully
fplant:submitErrorfalseOn 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 )TypeDescription
formIdstringForm ID
formHTMLElementForm 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 )TypeDescription
fieldNamestringField name
valueanyCurrent value of the field
fieldHTMLElementField element
groupHTMLElementField group element
errorMessagestring / nullError 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
}
}
});
Key point

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 function
  • fplant: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 )TypeDescription
formIdstringForm ID
formDataobjectThe 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 )TypeDescription
fieldErrorsobjectPer-field error messages ( { fieldName: 'error text', ... } )
messagestringThe 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 )TypeDescription
loadingbooleantrue: 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 )TypeDescription
formIdstringForm ID
confirmationElHTMLElementThe 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 )TypeDescription
formIdstringForm 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 )TypeDescription
action_typestringPost-submission action ( 'message' / 'redirect' / 'custom_page' )
messagestringSuccess message
redirect_urlstringRedirect destination URL ( when action_type is 'redirect' )
success_page_htmlstringCustom 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 )TypeDescription
messagestringError message
errorsobjectPer-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:validateField event does not fire ( there is no client-side validation )
  • window.fplant.addValidator() / removeValidator() are not available
  • The detail.formData of fplant:beforeSubmit is not included ( only formId )
  • The detail.confirmationEl of fplant:confirmationShow is not included ( only formId )