Skip to main content

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 global freeform.js and freeform.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. If form 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 main freeform.js and freeform.css files)
      • freeform.js-test
      • recaptcha.v2-invisible
      • recaptcha.v2-checkbox
      • recaptcha.v3
      • hcaptcha.invisible
      • hcaptcha.checkbox
      • turnstileNew in 5.6.5+
      • stripe
      • field.calculationNew in 5.8.0+

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> element
  • freeform - the Freeform JS Plugin instance
  • options - 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 return true or nothing to allow the form to be submitted, or false to prevent the form from being submitted.
  • isBackButtonPressed - a boolean 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> element
  • freeform - the Freeform JS Plugin instance
  • data - a FormData object with all of the POST values added to it
  • request - an XMLHttpRequest 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> element
  • freeform - the Freeform JS Plugin instance
  • request - an XMLHttpRequest object that was executed
  • response - the response JSON 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> element
  • freeform - the Freeform JS Plugin instance
  • request - an XMLHttpRequest object that was executed
  • response - the response JSON object, already deserialized
  • errors - 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> element
  • freeform - the Freeform JS Plugin instance
  • request - an XMLHttpRequest object that was executed
  • response - the response JSON 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> element
  • freeform - 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> element
  • freeform - the Freeform JS Plugin instance
  • errors - 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> element
  • freeform - the Freeform JS Plugin instance
  • errors - 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> element
  • freeform - 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> element
  • freeform - the Freeform JS Plugin instance
  • field - 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 when showProcessingText 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 - the css class name(-s) to be attached to the error banner html element
  • [string] errorClassList - the css class name(-s) to be attached to the error <ul> list element for each field with errors
  • [string] errorClassField - the css class name(-s) to be attached to the input field that has errors
  • [string] successClassBanner - the css 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.

VariableDescription
fontFamilyThe font family used throughout Elements. Elements supports custom fonts by passing the fonts option to the Elements group.
fontSizeBaseThe 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.
spacingUnitThe base spacing unit that all other spacing is derived from. Increase or decrease this value to make your layout more or less spacious.
borderRadiusThe border radius used for tabs, inputs, and other components in the Element.
colorPrimaryA primary color used throughout the Element. Set this to your primary brand color.
colorBackgroundThe color used for the background of inputs, tabs, and other components in the Element.
colorTextThe default text color used in the Element.
colorDangerA 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.

PropertyDescription
defaultCollapsed: trueControls 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: trueRenders 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: falseWhen true, the Payment Methods render as standalone buttons with space in between them. This property is only applicable to the accordion layout.
visibleAccordionItemsCount: 2Sets 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
// ...
}
);