Freeform Documentation

Learn the basics, and get to know Freeform inside and out.

Freeform 1.x Docs

Submitting a form using AJAX

To submit a form using AJAX - pass the serialized form data as the payload when posting to any front-end URL.

NOTE: This solution currently will not work with multi-page forms.

Return values #

The AJAX request must be a post request and it will return a JSON object with the following values:

On successful single-page form post #

  • success - A boolean value of true
  • finished - A boolean value of true
  • returnUrl - The return URL specified for the form
  • submissionId - An int value of the submission ID if one was generated
  • honeypot - A JS object containing these values:
    • name - the generated input name of the honeypot field
    • hash - the generated hash value that has to be submitted for the honeypot to validate

On form error #

  • success - A boolean value of false
  • finished - A boolean value of false
  • formErrors - An array of translatable form error messages
  • honeypot - A JS object containing these values:
    • name - the generated input name of the honeypot field
    • hash - the generated hash value that has to be submitted for the honeypot to validate
  • errors - An object of field handles as keys and each containing an array of error messages.
    • An example, if the form's firstName and lastName fields were required, but not filled out, the returning object would be:
"errors": {
    "firstName": ["This field is required"],
    "lastName": ["This field is required"]
}

Usage in Templates #

NOTE: To see a variety of complete working AJAX examples for the most popular frameworks, install and check out our Demo Templates included with Freeform. The JS used in the demo templates work in the latest browsers with plain vanilla javascript and do not require any JS libraries, like jQuery, etc.

Here's an example of the JS code that turns any form into an AJAX-ready form. It strives to cover most use-cases and allows for easy customization:

<script>

  function lookForFormsToAjaxify() {
    const forms = document.getElementsByTagName("form");

    for (const form of forms) {
      if (!form.dataset.ajaxified) {
        form.dataset.ajaxified = true;
        form.addEventListener("submit", ajaxifyForm, false);
      }
    }
  }

  function ajaxifyForm(event) {
    const form = event.target;
    const data = new FormData(form);
    const request = new XMLHttpRequest();

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

    request.open(method, action ? action : window.location.href, true);
    request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    request.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest");
    request.onload = function () {
      removeMessages(form);

      if (request.status === 200) {
        const response = JSON.parse(request.response);

        if (response.success && response.finished) {
          // Reset the form so that the user may enter fresh information
          form.reset();

          // ============================================================
          // Uncomment this to have the form redirect to the success page
          // ============================================================
          // if (response.returnUrl) {
          //   window.location.href = response.returnUrl;
          // }

          renderFormSuccess(form);

        } else if (response.errors || response.formErrors) {
          renderErrors(response.errors, form);
          renderFormErrors(response.formErrors, form);
        }

        if (response.honeypot) {
          const 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;
        }

        unlockSubmit(form);
      } else {
        console.error(request);
      }

      unlockSubmit(form);
    };

    request.send(data);
    event.preventDefault();
  }

  function loadExternalForm(url, targetElement) {
    const request = new XMLHttpRequest();

    // Load the forms content into the #form-loader div
    request.open("GET", url, true);
    request.send();
    request.onload = function () {
      if (request.status === 200) {
        targetElement.innerHTML = request.response;

        // Activate all of the loaded scripts
        const loadedScripts = targetElement.getElementsByTagName('script')
        for (const script of loadedScripts) {
          if (script.getAttribute('src')) {
            const newScript = document.createElement('script');
            newScript.setAttribute('src', script.getAttribute('src'));
            document.body.appendChild(newScript);
          } else {
            eval(script.innerHTML)
          }
        }

        lookForFormsToAjaxify();
      } else {
        console.error(request);
      }
    };
  }

  /**
   * Remove the "disabled" state of the submit button upon successful submit
   *
   * @property form
   */
  function unlockSubmit(form) {
    form.querySelector("button[type=submit]").removeAttribute("disabled");
    if (typeof grecaptcha !== 'undefined') {
      grecaptcha.reset();
    }
  }

  // Add remove prototypes
  Element.prototype.remove = function () {
    this.parentElement.removeChild(this);
  };

  NodeList.prototype.remove = HTMLCollection.prototype.remove = function () {
    for (let i = this.length - 1; i >= 0; i--) {
      if (this[i] && this[i].parentElement) {
        this[i].parentElement.removeChild(this[i]);
      }
    }
  };

  if (window.renderFormSuccess === undefined) {
    function renderFormSuccess(form) {
      const successMessage = document.createElement("div");
      successMessage.classList.add("alert", "alert-success", "form-success");

      const paragraph = document.createElement("p");
      paragraph.classList.add("lead");
      paragraph.appendChild(document.createTextNode("Form submitted successfully"));

      successMessage.appendChild(paragraph);

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

  if (window.removeMessages === undefined) {
    function removeMessages(form) {
      // Remove any existing errors that are being shown
      form.querySelectorAll("ul.errors").remove();
      const fieldsWithErrors = form.querySelectorAll(".has-error");
      for (field of fieldsWithErrors) {
        field.classList.remove("has-error");
      }

      // Remove success messages
      form.querySelectorAll(".form-success").remove();
      document.getElementsByClassName("form-errors").remove();
    }
  }

  if (window.renderErrors === undefined) {
    /**
     * @param errors
     * @param form
     */
    function renderErrors(errors, form) {
      for (const key in errors) {
        if (!errors.hasOwnProperty(key) || !key) {
          continue;
        }

        const messages = errors[key];
        const errorsList = document.createElement("ul");
        errorsList.classList.add("errors", "help-block");

        for (const message of messages) {
          const listItem = document.createElement("li");
          listItem.appendChild(document.createTextNode(message));
          errorsList.appendChild(listItem);
        }

        const inputList = form.querySelectorAll("*[name=" + key + "], *[name='" + key + "[]']");
        for (const input of inputList) {
          input.parentElement.classList.add("has-error");
          input.parentElement.appendChild(errorsList);
        }
      }
    }
  }

  if (window.renderFormErrors === undefined) {
    function renderFormErrors(errors, form) {
      const errorBlock = document.createElement("div");
      errorBlock.classList.add("alert", "alert-danger", "form-errors");

      const paragraph = document.createElement("p");
      paragraph.classList.add("lead");
      paragraph.appendChild(document.createTextNode("This form has errors"));
      errorBlock.appendChild(paragraph);

      if (errors.length) {
        const errorsList = document.createElement("ul");
        for (const message of errors) {
          const listItem = document.createElement("li");
          listItem.appendChild(document.createTextNode(message));
          errorsList.appendChild(listItem);
        }

        errorBlock.appendChild(errorsList);
      }

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

  lookForFormsToAjaxify();

</script>

If you're caching a form and need to manually refresh the CSRF token, you may need something like this:

<div id="target"></div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
  $(() => {
    $.ajax({
      url: "/ajax",
      success: (response) => {
        $("#target").html(response);
        $('input[name={{ craft.app.config.general.csrfTokenName }}]').val('{{ craft.app.request.csrfToken }}');
      }
    });
  });
</script>