This document is for an older version of

Freeform

.

View latest version →

Templating

AJAX Forms with Freeform Revised 3.10.0+

Welcome to the guide for submitting your forms using AJAX. The good news is that we've made it easy to plug and play!

TIP

If you're using a Freeform version older than 3.10, please click here to view the v3.9 documentation that shows the old approach for usage of AJAX with the Freeform Javascript plugin.

Overview

Freeform has its own built-in automated AJAX support. To enable it, simply check the Enable AJAX checkbox in the Form Settings tab inside the Form Builder and Freeform will then handle the rest. Freeform includes its own javascript plugin which handles AJAX functionality and all other JS needed (such as Date fields, Stripe payments and more). Much of the default functionality (formatting, language, etc) can be overridden if necessary. See customization reference guide below.

TIP

When using Freeform's Stripe Payments feature, Freeform will automatically force AJAX mode on the form in order to work with Stripe's validation.

If you need to use a manual approach for the AJAX call, usually because some sort of framework is used to generate the form such as Vue.js, please see the Manual Implementations guide further below.

TIP

Be sure to check out the Freeform Javascript plugin documentation, to learn how to further extend Freeform's functionality.

Multi-page forms now work with the built-in AJAX feature. 3.10.0+

WARNING

  • Editing existing submissions via the front end in multi-page AJAX forms will currently not work correctly. We hope to be able to resolve this issue in an upcoming release.
  • If loading the same form more than once in the same template, be sure to specify the id parameter for each instance so that AJAX submit can account for this.
  • In versions prior to Freeform 3.10.0, AJAX will not work with multi-page forms.

Return URLs

When using Freeform's built-in AJAX, Freeform will by default stay on the same page and just show a success banner. To have Freeform perform a redirect to the return URL specified in the form builder upon successful AJAX submit, you'll need to add some JS to your page template. See example below:

This will apply itself to any form








 
 
 
 
 
 
 
 
 
 
 
 
 




<html>
<head>
</head>
<body>

    {{ craft.freeform.form("myFormHandle").render() }}

    <script>
      // Do something on a successful ajax submit
      document.addEventListener('freeform-ajax-success', function(event) {
        // Redirect the user
        if (event.response.finished) {
          window.location.href = event.response.returnUrl;
        }
      })
      document.addEventListener("freeform-render-success", function (event) {
      // Stop the default success message rendering
        event.preventDefault();
      });
    </script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

This will apply itself to a particular form








 
 
 
 
 
 
 
 
 
 
 




<html>
<head>
</head>
<body>

    {{ craft.freeform.form("myFormHandle", {id: 'my-form'}).render() }}

    <script>
      // Find the form element
      const myForm = document.getElementById('my-form');
      // Do something on a successful ajax submit
      myForm.addEventListener('freeform-ajax-success', function(event) {
        // Redirect the user
        if (event.response.finished) {
          window.location.href = event.response.returnUrl;
        }
      })
    </script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Not Using a Formatting Template 3.13.11+

When not using a formatting template, this can cause issues with some of Freeform's AJAX autoload features. When using AJAX and the Reload Form with Success Message (or No Effect) success behavior, Freeform will swap the form contents with the formatting template specified in the form builder for that form (whether or not you're actually using it). This can have an undesirable results. If you wish to just have Freeform reload your existing formatting markup (inside the regular template), you can add the data-skip-html-reload attribute to the form. This can be added one of 2 ways:

  1. Inside the form builder:
  • Open the form builder for the given form.
  • Open the form settings tab (cog/gear icon).
  • In the Form tag Attributes area, add data-skip-html-reload as the Attribute and leave the Value empty.
  • Save the form.
  1. Inside your regular template:
  • Open the regular template that contains the form.
  • Add the formAttributes: { "data-skip-html-reload": true }, parameter to your freeform.form function.

Alternate Success Markup

If you wish to override the default AJAX success message and instead load alternate success markup from a template or existing div, check out our guide for this:

Display Success Banner at Bottom

If you wish to display the success banner at the bottom of the form, here's an example of how to do so. The success banner will have the class ff-form-success added to it, which will allow it to be automatically removed again on consequent saves.

{{ craft.freeform.form("myForm").render }}

<script>
  var form = document.querySelector('form');
  form.addEventListener('freeform-render-success', function (event) {
    event.preventDefault();
    
    var freeform = event.freeform;
    var success = document.createElement('div');
    success.classList.add(freeform.options.successClassBanner);
    success.appendChild(document.createTextNode('Form submitted successfully'));

    form.appendChild(success);
  });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Customization

The built-in AJAX functionality lets you completely customize the way your forms work if you're not satisfied with anything in the provided defaults. If you only need to customize the error messages, you can do so by overriding the defaults like this:

// Change success and error messages for all forms on this page
document.addEventListener('freeform-ready', function(event) {
  // Customize the error and success messages
  event.options.successBannerMessage = 'My custom success message';
  event.options.errorBannerMessage = 'My custom error message';
});

// You can also add this only to a specific form, by listening to the
// "freeform-ready" event on the specific form.
const form = document.getElementById('my-form');
form.addEventListener('freeform-ready', function (event) {
  // Customize the error and success messages
  event.options.successBannerMessage = 'My custom success message';
  event.options.errorBannerMessage = 'My custom error message';
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

A more elaborate example below shows how you can customize the way error and success messages are rendered:

// Find the form element
const form = document.getElementById('my-form');
form.addEventListener('freeform-ready', function(event) {
  // Override the error and success message element class names
  event.options.errorClassBanner = 'my-custom-error-banner';
  event.options.errorClassList = 'my-custom-errors-list';
  event.options.errorClassField = 'this-field-has-errors';
  event.options.successClassBanner = 'my-custom-success-banner';
});


// Override the way those messages are removed
form.addEventListener('freeform-remove-messages', function () {
  // Prevent the default behaviour
  event.preventDefault();

  form.querySelectorAll('.my-custom-error-banner').forEach(function (element) { element.remove() });
  form.querySelectorAll('.my-custom-success-banner').forEach(function (element) { element.remove() });
  form.querySelectorAll('.my-custom-errors-list').forEach(function (element) { element.remove() });
});

// Override the way form submit success message is displayed
form.addEventListener('freeform-render-success', function() {
  // Prevent the default behaviour
  event.preventDefault();

  const successMessage = document.createElement("div");
  successMessage.classList.add("my-custom-success-banner");
  successMessage.appendChild(document.createTextNode("Form submitted successfully"));

  form.insertBefore(successMessage, this.form.childNodes[0]);
});

// Override the way form errors are displayed
form.addEventListener('freeform-render-form-errors', function(event) {
  // Prevent the default behaviour
  event.preventDefault();

  const errors = event.errors;
  const errorBlock = document.createElement("div");
  errorBlock.classList.add("my-custom-errors-banner");

  const paragraph = document.createElement("p");
  paragraph.appendChild(document.createTextNode("Form contains errors!"));
  errorBlock.appendChild(paragraph);

  if (errors.length) {
    const errorsList = document.createElement("ul");
    for (let messageIndex = 0; messageIndex < errors.length; messageIndex++) {
      const message = errors[messageIndex];
      const listItem = document.createElement("li");

      listItem.appendChild(document.createTextNode(message));
      errorsList.appendChild(listItem);
    }

    errorBlock.appendChild(errorsList);
  }

  form.insertBefore(errorBlock, this.form.childNodes[0]);
});

// Override the way field errors are displayed
form.addEventListener('freeform-render-field-errors', function(event) {
  // Prevent the default behaviour
  event.preventDefault();

  const errors = event.errors;
  for (const key in errors) {
    if (!errors.hasOwnProperty(key) || !key) {
      continue;
    }

    const messages = errors[key];
    const errorsList = document.createElement("ul");
    errorsList.classList.add("my-custom-errors-list");

    for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
      const message = messages[messageIndex];
      const listItem = document.createElement("li");
      listItem.appendChild(document.createTextNode(message));
      errorsList.appendChild(listItem);
    }

    const inputList = form.querySelectorAll("*[name=" + key + "], *[name='" + key + "[]']");
    for (let inputIndex = 0; inputIndex < inputList.length; inputIndex++) {
      const input = inputList[inputIndex];
      input.classList.add("this-field-has-errors");
      input.parentElement.appendChild(errorsList);
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

Advanced

To attach your own functionality when the AJAX form successfully submits or fails, you have to listen to the respective events dispatched by the Freeform JS plugin.
For instance, redirecting after an AJAX submit is finished:

// Find the form element
const form = document.getElementById('my-form');

// Do something on a successful ajax submit
form.addEventListener('freeform-ajax-success', function(event) {
  const response = event.response;

  // Redirect the user
  window.location.href = response.returnUrl;
})

// Do something on a failed ajax submit
form.addEventListener('freeform-ajax-error', function(event) {
  // Do whatever checks on errors you need to do, if any
  const errors = event.errors;
  const formErrors = event.formErrors;
  const response = event.response;

  // Redirect the page using the response's return URL
  window.location.href = response.returnUrl;
})

// Do something after every ajax submit is completed, regardless of it being successful or not
form.addEventListener('freeform-ajax-after-submit', function(event) {
  alert('the AJAX call was finished.');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Manual Implementations

There are cases when an AJAX call has to be made manually. Usually it is because some sort of framework is used to generate the form, such as Vue.js. All you need to do when making a manual AJAX request is pass the AJAX specific headers within the request:

{
  "X-Requested-With": "XMLHttpRequest"
  "HTTP_X_REQUESTED_WITH": "XMLHttpRequest"
}
1
2
3
4

TIP

By default, Freeform will assume you're using its own built-in AJAX. To prevent Freeform from interfering with your custom AJAX implementation, you should add a data-skip-html-reload form attribute to your form.

Here are some examples:

Plain JS

To create an AJAX request using regular plain javascript, follow this example:

// Find the form element
var form = document.getElementById("my-form");

// Add the on-submit event listener
form.addEventListener("submit", ajaxifyForm, false);

function ajaxifyForm(event) {
  var form = event.target;
  var data = new FormData(form);

  var method = form.getAttribute("method");
  var action = form.getAttribute("action");

  var request = new XMLHttpRequest();
  request.open(method, action ? action : window.location.href, true);
  request.setRequestHeader("Cache-Control", "no-cache");

  // Set the Craft specific AJAX headers
  request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  request.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest");

  request.onload = function () {
    if (request.status === 200) {
      var response = JSON.parse(request.response);

      if (response.success && response.finished) {
        form.reset();
        alert("Form posted successfully");
      } else if (response.errors || response.formErrors) {
        console.log("Field Errors", response.errors);
        console.log("Form Errors", response.formErrors);

        alert("Form failed to submit");
      }

      // Update the Honeypot field if using JS enhancement
      if (response.honeypot) {
        var honeypotInput = form.querySelector("input[name^=freeform_form_handle_]");
        honeypotInput.setAttribute("name", response.honeypot.name);
        honeypotInput.setAttribute("id", response.honeypot.name);
        honeypotInput.value = response.honeypot.hash;
      }
    } else {
      console.error(request);
    }
  };

  request.send(data);
  event.preventDefault();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

Axios

To create an AJAX request using the Axios library, follow this example:

// Find the form element
var form = document.getElementById("my-form");

// Add the on-submit event listener
form.addEventListener("submit", ajaxifyForm, false);

function ajaxifyForm(event) {
  var form = event.target;
  var data = new FormData(form);

  var method = form.getAttribute("method");
  var action = form.getAttribute("action");

  axios({
    url: action ? action : window.location.href,
    method: method ? method : "post",
    data: data,
    headers: {
      "X-Requested-With": "XMLHttpRequest",
      HTTP_X_REQUESTED_WITH: "XMLHttpRequest",
    },
  })
    .then(function (responseObject) {
      var response = responseObject.data;

      if (response.success && response.finished) {
        form.reset();
        alert("Form posted successfully");
      } else if (response.errors || response.formErrors) {
        console.log("Field Errors", response.errors);
        console.log("Form Errors", response.formErrors);

        alert("Form failed to submit");
      }

      // Update the Honeypot field if using JS enhancement
      if (response.honeypot) {
        var honeypotInput = form.querySelector(
          "input[name^=freeform_form_handle_]",
        );
        honeypotInput.setAttribute("name", response.honeypot.name);
        honeypotInput.setAttribute("id", response.honeypot.name);
        honeypotInput.value = response.honeypot.hash;
      }
    })
    .catch(function (error) {
      console.error(error);
    });

  event.preventDefault();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51