Growth & Marketing

September 3, 2020

April 29, 2019

How to easily track form submissions without hacky code

Table of contents

Good bye to tracking the thank you page

I am sure you have ever seen people tracking this kind of conversions by page destinations, as an example,  some analytics tools like Google Analytics measure page views metric by default, so for unexperienced people this can be seen as the best and fastest option, because they only have to specify as a goal target the destination URL of the thank you page.

Even this is one of the most common approaches to track form submissions, this approach is very inaccurate because you can find lot of situations where this won't work: the page is closed before loading the thank you page, the page is reloaded, slow internet speed, etc and sometimes it's not even possible as the form doesn't redirect the user to a thank you page.

Tracking the form response status code

Many online forms send their data using the lovely jQuery library with $.ajax requests and others use the fetch or XMLHttpRequest APIs with pure JavaScript. All these methods allows you to make form requests without reloading or opening a new page.

Let's see some code examples.

$.ajax form request example:
$('#form-id').submit(function (e) {
  $.ajax({
    type: 'POST',
    url: 'https://www.example.com/endpoint',
    data: $('#form-id').serialize(),
    success: function () {
      console.log('You form has been submitted');
    },
    error: function () {
      console.log('There was an error sending your form');
    }
  });

  e.preventDefault();
});


fetch form request example:
var form = document.querySelector('#my-form');

form.addEventListener('submit', function (e) {
  var fetchOptions = {
    body: new FormData(form),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    method: 'post',
  }

  fetch('https://www.example.com/endpoint', fetchOptions)
    .then(function (res) {
      if (res.status !== 200) {
        console.log('There was an error sending your form');
      } else {
        console.log('You form has been submitted');
      }
    })
    .catch(function () {
      console.log('There was a network error');
    });

  e.preventDefault();
})

XMLHttpRequest form request example:
var form = document.querySelector('#my-form');

form.addEventListener('submit', function (e) {
  var xhr = new XMLHttpRequest();
  var formData = new FormData(form);

  xhr.addEventListener('load', function () {
    console.log('You form has been submitted');
  });

  xhr.addEventListener('error', function () {
    console.log('There was an error sending your form');
  });

  xhr.open('POST', 'https://www.example.com/endpoint');
  xhr.send(formData);

  e.preventDefault();
})


Both fetch and XMLHttpRequest APIs can be a bit tricky and verbose, but they are native browser APIs and you avoid loading a +200kb library in your page for just a simple form submission.

If you have access to the source code, all previous examples are pretty easy to handle, as you only have to place your tracking code on a success callback.

$.ajax({
  ...
  success: function () {
    analytics.track('formSubmit');
  },
  ...
});


This approach is much more accurate than tracking the thank you page as it will immediately execute your code after receiving a success status code response from your endpoint. But this is not a perfect solution, as it depends on how your backend endpoint handles the response, for example, if you receive a success status code (2xx) with a text saying "Sorry, but there was an internal error", your success callback will be executed anyway.

To fix this, you will need to modify your form backend endpoint and that's another story because it's not always possible. Unfortunately, form tracking might become a real pain and sometimes people will end up adding dirty pieces of code like this:

$.ajax({
  ...
  success: function (response) {
    var text = response.toLowerCase();
    if (text.includes('thank')) {
      analytics.track('formSubmit');
    }
  },
  ...
});


But I use Google Tag Manager and it has a native form submission trigger...

This GTM trigger is an abstraction of using:

document.addEventListener('submit', function(){
  console.log('You form has been submitted');
});

Most of the times this trigger doesn't work as it listens to the document form submit event and if you review again our previous code examples, you will see that all of them prevent the default submit behaviour using e.preventDefault(). Also take in mind this trigger will be executed when you submit a form regardless of your form response.

There is a Check validation option to handle previous situation but it also requires some workarounds that are not elegant solutions.

Hello to event-based form tracking

When we started coding our open source JavaScript SDK that embeds our forms in any page, we wanted to solve all this situations with an easy approach, so we decided the best option was to trigger custom DOM events based on how users interacted with our forms. As we cover all the form submission lifecycle this was a pretty easy task for us because we already followed best coding practices on both sides (frontend + backend).

List of custom DOM events:

Event Description
af-submitForm Fires when a form is submitted, regardless of the result.
af-submitForm-error Fires when there is an error submitting a form.
af-submitForm-success Fires when a form is submitted successfully.
af-invalidFields-error Fires when there are invalid fields.
af-previousStep Fires when you go to the previous form step.
af-nextStep Fires when you go the next form step.
af-blurField Fires when a field loses focus.
af-focusField Fires when a field gets focus.
af-changeField Fires when the value of a field has been changed.

Triggering this custom DOM events allows you to easily track user behaviour in your forms without hacky coding and using your favorite analytics tools.

Using track API from Segment:

document.addEventListener('af-submitForm-success', function (e) {
  analytics.track('formSubmit');
});


Using dataLayer from Google Tag Manager:

document.addEventListener('af-submitForm-success', function (e) {
  dataLayer.push({
    event: 'formSubmit'
  });
});

You can also access your form fields data inside the event handler and detail property:

document.addEventListener('af-submitForm-success', function (e) {
  console.log('You form has been submitted', e.detail);
});

Here is another example:

Coding forms by yourself can be time consuming as you have to take care about many small details and at first, you won't probably think about adding custom DOM events, but if you do, you will save a lot of time from people in your team that needs to measure something in your forms but then, form tracking will become as easy as adding a simple line of code in your document without having to modify the form source code again.When we talk about conversion rate, one of the most common actions we think about is a form submission. In this article, we will explain the most common approaches to track form submissions, capture form fields data and our approach at Arengu to ease this tasks and avoid hacky code.

You might like to read

Subscribe to our newsletter

Subscribe to our email newsletter to receive article notifications and regular product updates.