Freeform Javascript Plugin
Freeform includes its own JS plugin that is at the center of handling all of Freeform's powerful convenience features like AJAX, conditional rules, date pickers, table fields, Captchas, Stripe payments, and more!
Freeform is designed to be as lean as possible, and will only call the necessary scripts when the form is loaded inside your template. For example, if the form you're loading does not contain a Date field with datepicker, Freeform will not call the additional scripts that make this necessary.
In the event you wish to override or extend any of Freeform's default JS behavior, you can certainly do so by referring to the options below:
Getting Started
For the sake of this article, let's assume that you have this form in your page:
<form id="my-form" data-handle="mySpecificFormHandle" data-freeform>
... form content here ...
</form>
To hook into the Freeform JS plugin once the plugin has been initialised, your custom javascript has to listen to the freeform-ready
event either on the form
element or on the document
element.
Here's a small preview of the things possible:
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ready', function (event) {
const freeform = event.freeform;
const form = event.form;
// override options by changing the event.options object
// for a full list of available options, see the "Plugin Options" section below
event.options.ajax = true;
event.options.processingText = 'My Custom Loading Text';
});
myForm.addEventListener('freeform-on-submit', function (event) {
// Submit handling logic
});
// Or...
document.addEventListener('freeform-ready', function (event) {
const freeform = event.freeform;
const form = event.form;
// perform something on a specific form only
if (form.dataset.handle === 'mySpecificFormHandle') {
alert('Loaded "My Specific Form"');
// override options by changing the event.options object
// for a full list of available options, see the "Plugin Options" section below
event.options.ajax = true;
event.options.processingText = 'My Custom Loading Text';
}
form.addEventListener('freeform-on-submit', function (submitEvent) {
// Stop all forms from submitting
submitEvent.preventDefault();
});
});
document.addEventListener('freeform-on-submit', function (event) {
const form = event.form;
if (form.id === 'my-form') {
// Submit handling logic
}
});
When to call your Custom Scripts
You can add your custom form handler scripts anywhere in the page. The freeform-ready
event is triggered for each form and dispatched via the form
and document
elements. The events are dispatched once the DOM is ready. If you add a form to the page via AJAX, it will automatically get initialized with the Freeform
plugin and will also trigger the freeform-ready
event.
Loading Freeform JS manuallyImproved in 5.5+
If you wish to manually load the Freeform JS elsewhere in your template (rather than having Freeform automatically insert it inside the Freeform form or in the page footer), be sure to select the None
option for the Script Insertion Location setting. Then, you'll need to add the freeform.loadFreeformPlugin()
function to your template where you'd like it to insert the JS. You can pass two variables to the function to add custom attributes to the script and style tags that Freeform generates.
freeform.loadFreeformPlugin()
- will load Freeform's globalfreeform.js
andfreeform.css
files.freeform.loadFormSpecificScripts(form)
New in 5.5.3+ - will load form-specific scripts such as reCAPTCHA and the JS Test scripts, where applicable. Be sure to pass the form to this function. Ifform
is already defined, you can use that.freeform.loadScripts()
RecommendedNew in 5.5.4+ - capable of generating any of Freeform's scripts when you specify them.- Options are:
freeform
(loads Freeform's mainfreeform.js
andfreeform.css
files)freeform.js-test
recaptcha.v2-invisible
recaptcha.v2-checkbox
recaptcha.v3
hcaptcha.invisible
hcaptcha.checkbox
turnstile
New in 5.6.5+stripe
field.calculation
New in 5.8.0+
- Options are:
If you wish to generate Freeform's main scripts, the JS Test, reCAPTCHA v3 and Stripe, your template might look something like this:
{{ freeform.loadScripts([
"freeform",
"freeform.js-test",
"recaptcha.v3",
"stripe"
]) }}
loadScripts()
Example
Here's a simple example of how this may look in your template:
<body>
{# your template code #}
{{ freeform.loadScripts([
"freeform",
"freeform.js-test",
"recaptcha.v3",
]) }}
</body>
Will generate:
<body>
{# your template code #}
<link rel="stylesheet" href="https://site.test/cpresources/ff3a9010/css/front-end/plugin/freeform.css?v=1719558790" />
<script type="text/javascript" src="https://site.test/cpresources/ff3a9010/js/scripts/front-end/plugin/freeform.js?v=1719558790"></script>
<script type="text/javascript" src="https://site.test/cpresources/8c409078/js-test.js?v=1720148793"></script>
<script type="text/javascript" src="https://site.test/cpresources/ff3a9010/js/scripts/front-end/captchas/recaptcha/v3.js?v=1719558790"></script>
</body>
loadFreeformPlugin()
and loadFormSpecificScripts()
Example
Here's a simple example of how this may look in your template:
<body>
{# your template code #}
{{ freeform.loadFreeformPlugin }}
{{ freeform.loadFormSpecificScripts(form) }}
</body>
Will generate:
<body>
{# your template code #}
<link rel="stylesheet" href="https://site.test/cpresources/ff3a9010/css/front-end/plugin/freeform.css?v=1719558790" />
<script type="text/javascript" src="https://site.test/cpresources/ff3a9010/js/scripts/front-end/plugin/freeform.js?v=1719558790"></script>
<script type="text/javascript" src="https://site.test/cpresources/ff3a9010/js/scripts/front-end/captchas/recaptcha/v3.js?v=1719558790"></script>
<script type="text/javascript" src="https://site.test/cpresources/8c409078/js-test.js?v=1720148793"></script>
</body>
Passing Custom Attributes to loadFreeformPlugin()
:
<body>
{# your template code #}
{{ freeform.loadFreeformPlugin(
'data-custom="attribute" extra="something else"',
'css="stuff"'
) }}
</body>
Will generate:
<body>
{# your template code #}
<link href="https://site.test/cpresources/87e67e90/css/front-end/plugin/freeform.css?v=1716400868" css="stuff" rel="stylesheet">
<script src="https://site.test/cpresources/87e67e90/js/scripts/front-end/plugin/freeform.js?v=1716401083" data-custom="attribute" extra="something else"></script>
</body>
Form Events
Freeform emits various events which let you extend its functionality in various ways.
Plugin Initialized: freeform-ready
This is the plugin which lets you modify the initial form setup. Use this to change the class names and texts of error messages, success messages, etc.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instanceoptions
- an object which lets you modify the Plugin Options
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ready', function (event) {
event.options.ajax = true;
event.options.processingText = 'My Custom Loading Text';
});
On Submit: freeform-on-submit
This event is cancelable and lets you do something as soon as the form submit button is pressed. This event can be used to perform additional validation or anything else, really.
This event has the following properties:
form
- the<form>
element.freeform
- the Freeform JS Plugin instance.addCallback(callback: () => Promise<void | boolean>, prority?: number)
New in 5.0+ - an async function which lets you add a callback to be executed before the form is submitted. It will lock the submit process until finished. The callback should returntrue
or nothing to allow the form to be submitted, orfalse
to prevent the form from being submitted.isBackButtonPressed
- aboolean
value which specifies if the Back button was pressed or not.
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-on-submit', function (event) {
const form = event.form;
const isBackButtonPressed = event.isBackButtonPressed;
// Do not modify anything if the form is going back
if (isBackButtonPressed) {
alert('Going backwards!');
return;
}
const name = form.querySelector('input[name="firstName"]').value;
// Prevent anyone except Bob from submitting the form
if (name !== 'Bob') {
alert("You're not Bob");
event.preventDefault();
}
// Force everyone to always be a Smith
form.querySelector('input[name="lastName"]').value = 'Smith';
});
To add a blocking synchronous callback to the form submit process, you can use the addCallback
method:
// Priorities of the callbacks are 0 by default, and are optional.
// The bigger the priority, the faster the callback will be executed.
// The smaller the priority, the slower the callback will be executed.
// Negative numbers are allowed.
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-on-submit', function (event) {
// Add priority 100 to make sure this callback is executed before others
event.addCallback(async () => {
const json = await fetch('/some/long/api/call').then((response) =>
response.json()
);
if (json.errors.length) {
alert('There was an error');
return false;
}
}, 100);
// Add priority -100 to make sure this callback is executed after others
event.addCallback(async () => {
// execute some blocking code here
}, -100);
});
Right before AJAX submits: freeform-ajax-before-submit
This event is cancelable and lets you tinker with the XMLHttpRequest
object and perform anything else you'd like to do before the AJAX call is made.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instancedata
- aFormData
object with all of thePOST
values added to itrequest
- anXMLHttpRequest
object that is about to be executed
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ajax-before-submit', function (event) {
const form = event.form;
const payload = event.data;
const request = event.request;
// Attach a little present to the POST payload
payload.append('present', 'surprise');
// Set an additional header for the request
request.setRequestHeader('X-CUSTOM-HEADER', 'This is a custom header');
// And after all this hard work - we just prevent the form from submitting the AJAX data
event.preventDefault();
});
After a successful AJAX submission: freeform-ajax-success
If you wish to modify the success messages, there's a specific event for that: freeform-render-success
Alternatively you might consider the successBannerMessage
and successClassBanner
Freeform Plugin Options
Use this event to perform actions that should happen only if the form has been successfully posted via AJAX.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instancerequest
- anXMLHttpRequest
object that was executedresponse
- the responseJSON
object, already deserialized
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ajax-success', function (event) {
const form = event.form;
const response = event.response;
alert(`Successfully submitted Submission #${response.submissionId}`);
});
CUSTOM RETURN URL
If you need to control a custom return on the form, you would do that with the following code:
// Find the form element
const form = document.getElementById('my-form');
// Do something on a successful ajax submit
form.addEventListener('freeform-ajax-success', function (event) {
// Redirect the user
window.location.href = event.response.returnUrl;
});
After a failed AJAX submission: freeform-ajax-error
If you wish to modify the error messages, there are specific events for that: freeform-render-field-errors
and freeform-render-form-errors
Alternatively you might consider the errorBannerMessage
, errorClassBanner
, errorClassList
and errorClassField
Freeform Plugin Options
Use this event to handle cases when there was an error while submitting, such as a missing required field, etc.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instancerequest
- anXMLHttpRequest
object that was executedresponse
- the responseJSON
object, already deserializederrors
- an object of all field handles as keys containing an array of error message strings each. (e.g.{ firstName: ['This field is Required', 'Some other error'], lastName: ['error happened']
)formErrors
- an array of all form error strings (e.g.['invalid recaptcha', 'some other error']
)
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ajax-error', function (event) {
const form = event.form;
const errors = event.errors;
const formErrors = event.formErrors;
alert(`There were ${formErrors.length} form errors`);
});
After any AJAX submit: freeform-ajax-after-submit
This event happens after an AJAX submit is finished, regardless of the success or failure of it. This is usually used to perform some sort of reset logic. For instance Freeform uses this event to reset the captcha elements and stripe elements, since each request has to get new tokens.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instancerequest
- anXMLHttpRequest
object that was executedresponse
- the responseJSON
object, already deserialized
// Count the number of times the submit button was pressed
let numberOfSubmissions = 0;
const myForm = document.getElementById('my-form');
myForm.addEventListener('freeform-ajax-after-submit', function (event) {
const form = event.form;
numberOfSubmissions++;
});
Rendering success messages: freeform-render-success
This event is cancelable. Use this event to do something in addition to the default success message renderer. Or write your own success rendering logic by preventing the default behavior of the event.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instance
document.addEventListener('freeform-render-success', function (event) {
// Stop the default success message rendering
event.preventDefault();
const options = event.freeform.options;
const form = event.form;
const classNames = options.successClassBanner; // Get the success banner css class names
// create a custom banner <div>
const banner = document.createElement('div');
banner.classList.add(classNames);
banner.innerText = 'So successful. Omg.';
form.appendChild(banner);
});
Rendering form errors: freeform-render-form-errors
This event is cancelable and lets you override or complement the default form error rendering.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instanceerrors
- an array of all form error strings
document.addEventListener('freeform-render-form-errors', function (event) {
// Stop the default form error rendering
event.preventDefault();
const options = event.freeform.options;
const form = event.form;
const errors = event.errors;
const classNames = options.errorClassBanner; // Get the error banner css class names
// create a custom banner <div>
const banner = document.createElement('div');
banner.classList.add(classNames);
banner.innerText = `There are ${errors.length} errors in this form`;
form.appendChild(banner);
});
Rendering field errors: freeform-render-field-errors
This event is cancelable. Use this event to do something on field error rendering, or write your own field rendering logic by preventing the default behavior of the event.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instanceerrors
- an object with field handles as keys, that contains an array of error messages for each key.
document.addEventListener('freeform-render-field-errors', function (event) {
// Stop the default field error rendering
event.preventDefault();
const form = event.form;
const errors = event.errors;
for (const fieldHandle in errors) {
// Log each field errors to the console
console.error(
'%s has the following errors: %s',
fieldHandle,
errors[fieldHandle].join('; ')
);
}
});
Removing success and error banner messages: freeform-remove-messages
This event is cancelable and lets you handle the removal of success and error messages. It makes sense to use this only if you are doing your own success and error rendering.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instance
document.addEventListener('freeform-remove-messages', function (event) {
const form = event.form;
// Find every element with the class "error"
const allErrorElements = form.querySelectorAll('.error');
allErrorElements.forEach(function (element) {
// Remove element from the DOM
element.remove();
});
});
Removing field messages: freeform-remove-field-messages
This event is cancelable and lets you handle the removing of field specific error messages. It makes sense to use this only if you are doing your own field error rendering logic. Event then, you might be able to do all of it in the Removing success and error banner messages event.
This event has the following properties:
form
- the<form>
elementfreeform
- the Freeform JS Plugin instancefield
- the field input element
document.addEventListener('freeform-remove-field-messages', function (event) {
const form = event.form;
const field = event.field;
// <input type="text" class="my-custom-error-class" />
// Removing the class name of this field's input element
field.classList.remove('my-custom-error-class');
// Removing a <div class="my-custom-errors-block"> which
// is located right after the field's input element
const errorBlock = field.parentElement.querySelector(
'.my-custom-errors-block'
);
errorBlock.remove();
});
Plugin Options
The Freeform JS Plugin has several options that let you override the way it works. Mainly, the options are for rendering AJAX form success or failure messages.
You can override the options by passing your own option values during the freeform-ready
event
const form = document.getElementById('my-form');
form.addEventListener('freeform-ready', function (event) {
// Enable AJAX. This happens automatically if you enable AJAX via the form builder
event.options.ajax = true;
event.options.successBannerMessage = 'Custom success banner message here';
event.options.successClassBanner =
'my-success-class additional-success-css-class';
});
The available options are:
- [
bool
]ajax
- Determines if AJAX is used on this form or not - [
bool
]disableSubmit
- Enables the automatic disabling/enabling of submit buttons once the form is submitted - [
bool
]autoScroll
- Will automatically scroll to the top of the form upon submit - [
bool
]showProcessingSpinner
- Shows the spinner icon in the submit button when submitted - [
bool
]showProcessingText
- Changes the text of the submit button when the form is submitted - [
string
]processingText
- The text to use whenshowProcessingText
is enabled - [
string
]successBannerMessage
- The text that will be shown in the success banner - [
string
]errorBannerMessage
- The text that will be shown in the error banner - [
string
]errorClassBanner
- thecss
class name(-s) to be attached to the error banner html element - [
string
]errorClassList
- thecss
class name(-s) to be attached to the error<ul>
list element for each field with errors - [
string
]errorClassField
- thecss
class name(-s) to be attached to the input field that has errors - [
string
]successClassBanner
- thecss
class name(-s) to be attached to the success banner html element
Stripe Payments EventsImproved in 5.0+
To style the Stripe Payment Element, you'll need to apply Stripe's Appearance API variables to the elementOptions
and paymentOptions
listeners.
elementOptions.appearance
Below is a list of commonly used variables. See Stripe documentation for a complete list of other variables.
Variable | Description |
---|---|
fontFamily | The font family used throughout Elements. Elements supports custom fonts by passing the fonts option to the Elements group. |
fontSizeBase | The font size that's set on the root of the Element. By default, other font size variables like fontSizeXs or fontSizeSm are scaled from this value using rem units. |
spacingUnit | The base spacing unit that all other spacing is derived from. Increase or decrease this value to make your layout more or less spacious. |
borderRadius | The border radius used for tabs, inputs, and other components in the Element. |
colorPrimary | A primary color used throughout the Element. Set this to your primary brand color. |
colorBackground | The color used for the background of inputs, tabs, and other components in the Element. |
colorText | The default text color used in the Element. |
colorDanger | A color used to indicate errors or destructive actions in the Element. |
The rules
option is a map of CSS-like selectors to CSS properties, allowing granular customization of individual components. After defining your theme
and variables
, use rules
to seamlessly integrate Elements to match the design of your site.
The selector for a rule can target any of the public class names in the Element, as well as the supported states, pseudo-classes, and pseudo-elements for each class. For example, the following are valid selectors:
.Tab
,.Label
,.Input
.Tab:focus
.Input--invalid
,.Label--invalid
.Input::placeholder
The following are not valid selectors:
.p-SomePrivateClass
,img
- only public class names can be targeted.Tab .TabLabel
- ancestor-descendant relationships in selectors are unsupported.Tab--invalid
- the.Tab
class does not support the--invalid
state
See Stripe documentation for a complete list.
paymentOptions.layout
Options for creating the Payment Element.
Property | Description |
---|---|
defaultCollapsed: true | Controls if the Payment Element renders in a collapsed state (where no payment method is selected by default). When you leave this undefined, Stripe renders the experience that it determines will have the best conversion. |
radios: true | Renders each Payment Method with a radio input next to its logo. The radios visually indicate the current selection of the Payment Element. This property is only applicable to the accordion layout. |
spacedAccordionItems: false | When true, the Payment Methods render as standalone buttons with space in between them. This property is only applicable to the accordion layout. |
visibleAccordionItemsCount: 2 | Sets the max number of Payment Methods visible before using the "More" button to hide additional Payment Methods. Set this value to 0 to disable the "More" button and render all available Payment Methods. Default is 5 . This property is only applicable to the accordion layout. |
Full documentation can be found here: Stripe Elements Appearance API
Example
<script>
var form = document.querySelector('[data-id="{{ form.anchor }}"]');
if (form) {
form.addEventListener('freeform-stripe-appearance', function (event) {
event.elementOptions.appearance = Object.assign(
event.elementOptions.appearance,
{
variables: {
colorPrimary: '#0d6efd',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"',
fontSizeBase: '16px',
spacingUnit: '0.2em',
tabSpacing: '10px',
gridColumnSpacing: '20px',
gridRowSpacing: '20px',
colorText: '#eaeaea',
colorBackground: '#1d1f23',
colorDanger: '#dc3545',
borderRadius: '5px',
},
rules: {
'.Tab, .Input': {
border: '1px solid #6c757d',
boxShadow: 'none',
},
'.Tab:focus, .Input:focus': {
border: '1px solid #0b5ed7',
boxShadow: 'none',
outline: '0',
transition: 'border-color .15s ease-in-out',
},
'.Label': {
fontSize: '16px',
fontWeight: '400',
},
},
}
);
event.paymentOptions.layout = Object.assign(event.paymentOptions.layout, {
defaultCollapsed: true,
visibleAccordionItemsCount: 3,
});
});
}
</script>
Date Picker Events
The Freeform Date & Time field comes packed with a built-in, optional flatpickr instance. Check out the Date Picker Events documentation for full customization options of the flatpickr instance.
Autoscroll Events
When using the Automatically Scroll to Form on Errors and Multipage forms? setting, Freeform will automatically handle scrolling the the browser down to the form. However, if you have a floating header or other situation you need to account for, you can override the scroll offset as well as the anchor to scroll to. You can attach your own logic to the events by listening on the following events:
document.addEventListener('freeform-ready', (event) => {
// scrolling offset in pixels from the top of the form.
// defaults to 0
event.options.scrollOffset = -400;
});
document.addEventListener('freeform-ready', (event) => {
// element to call `.scrollTo()` on
// defaults to `window`
// using `document.getElementById('main');` as an example
event.options.scrollElement = document.getElementById('main');
});
Table Field Events
The Freeform Table field comes with its own optional javascript functionality for adding and removing rows. You can attach your own logic to the events by listening on the following events:
const form = document.getElementById('my-form');
form.addEventListener('freeform-field-table-on-add-row', function (event) {
const form = event.form;
const freeform = event.freeform;
const table = event.table; // The table element
const row = event.row; // The row element that will be added
// Do something before the row is added to the DOM
// ...
});
const form = document.getElementById('my-form');
form.addEventListener('freeform-field-table-after-row-added', function (event) {
const form = event.form;
const freeform = event.freeform;
const table = event.table; // The table element
const row = event.row; // The row element that will be added
// Do something after the row has been added to the DOM
// ...
});
const form = document.getElementById('my-form');
form.addEventListener('freeform-field-table-on-remove-row', function (event) {
const form = event.form;
const freeform = event.freeform;
const table = event.table; // The table element
const row = event.row; // The row element that will be removed
// Do something before the row is removed from the DOM
// ...
});
const form = document.getElementById('my-form');
form.addEventListener(
'freeform-field-table-after-remove-row',
function (event) {
const form = event.form;
const freeform = event.freeform;
const table = event.table; // The table element
// Do something after the row is removed from the DOM
// ...
}
);