AJAX Forms with Freeform

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

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 Composer 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.

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.

WARNING

AJAX currently will not work with multi-page forms.

Placement of Script Overrides

The placement of your JS overrides will depend on what your Freeform Javascript Insertion Location setting is set to. This is general information that will apply to all scenarios further below (light customization to advanced options). See those options for more complete solutions.

When using Inside Form option, the Freeform JS plugin instance will be accessible to scripts that are called inside the footer. Load your custom script in the footer and call the Freeform JS plugin instance directly from the form object, like so:

const form = document.getElementById("my-form");
form.freeform.setOption("successBannerMessage", "This is a custom success message.");
1
2

If you have set the Freeform Javascript Insertion Location setting set to Page Footer, then it's best to attach an event listener to the form object which will execute once the Freeform JS plugin instance has finished initializing. Use this script before the rest of the footer scripts, but after the form has been rendered, like so:

const form = document.getElementById("my-form");
form.addEventListener("freeform-ready", function (event) {
  event.target.freeform.setOption("successBannerMessage", "This is a custom success message.");
});
1
2
3
4

Multiple Forms in the Same Page

If you have multiple forms in the same page, and wish to have a single set of JS overrides, you can loop through all forms like this:

If Javascript Insertion Location setting is set to Inside Form:

<script>
    const forms = document.querySelectorAll('form[data-id]');
    for (const form of forms) {
      const freeform = form.freeform;

      freeform.setOption('successBannerMessage', "This is a custom success message.");
    }
</script>
1
2
3
4
5
6
7
8
<script>
  const forms = document.querySelectorAll('form[data-id]');
  for (const form of forms) {
    form.addEventListener("freeform-ready", function (event) {
      const freeform = event.target.freeform;
      freeform.setOption('successBannerMessage', "This is a custom success message");
    });
  }
</script>
1
2
3
4
5
6
7
8
9

TIP

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.

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:

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

// Attach an on-ready listener
// Be sure to do load this script before freeform JS initiates
form.addEventListener('freeform-ready', function(event) {
  const freeform = event.target.freeform;

  // Customize the error and success messages
  freeform.setOption('successBannerMessage', 'My custom success message');
  freeform.setOption('errorBannerMessage', 'My custom error message');
});
1
2
3
4
5
6
7
8
9
10
11
12

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');

// Attach on-ready listener
// Be sure to do load this script before freeform JS initiates
form.addEventListener('freeform-ready', function(event) {
  const freeform = event.target.freeform;

  // Override the error and success message element class names
  freeform.setOption('errorClassBanner', 'my-custom-error-banner');
  freeform.setOption('errorClassList', 'my-custom-errors-list');
  freeform.setOption('errorClassField', 'this-field-has-errors');
  freeform.setOption('successClassBanner', 'my-custom-success-banner');

  // Override the way those messages are removed
  freeform.setOption('removeMessages', function() {
    this.form.querySelectorAll('.my-custom-error-banner').remove();
    this.form.querySelectorAll('.my-custom-success-banner').remove();
    this.form.querySelectorAll('.my-custom-errors-list').remove();
  });

  // Override the way form submit success message is displayed
  freeform.setOption('renderSuccess', function() {
    const successMessage = document.createElement("div");
    successMessage.classList.add("my-custom-success-banner");
    successMessage.appendChild(document.createTextNode("Form submitted successfully"));

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

  // Override the way form errors are displayed
  freeform.setOption('renderFormErrors', function(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);
    }

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

  // Override the way field errors are displayed
  freeform.setOption('renderFieldErrors', function(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 = this.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

Advanced

To attach your own functionality when the AJAX form successfully submits or fails, you have to register your callbacks in the Freeform JS plugin. For instance, redirecting after an AJAX submit is finished:

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

// Attach on-ready listener
// Be sure to do load this script before freeform JS initiates
form.addEventListener('freeform-ready', function(event) {
  const freeform = event.target.freeform;

  // Do something on a successful ajax submit
  freeform.addOnSuccessfulAjaxSubmit(function(event, form, response) {
    // Redirect the user
    window.location.href = response.returnUrl;
  })

  // Do something on a failed ajax submit
  freeform.addOnFailedAjaxSubmit(function(event, form, response) {
    // Do whatever checks on errors you need to do, if any
    var errors = response.errors;

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

  // Return the form to the specified Return URL upon success
  freeform.addOnAfterAjaxSubmit((event, form, response) => {
    if (response.returnUrl) {
      window.location.href = response.returnUrl;
    }
  })

  // Do something after every ajax submit is completed, regardless of it being successful or not
  freeform.addOnAfterAjaxSubmit(function(event, form, response) {
    // Redirect the page using the response's return URL
    window.location.href = response.returnUrl;
  })
});
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

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

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
Last Updated: 10/10/2019, 7:46:18 AM