Tracking HubSpotV4 Forms & Multi-Step Forms In GTM

This post covers tracking forms created in the new HubSpot form editor that use API Version 4. This post also covers multi-step form navigation events and extracting field values (such as email) to the data layer.

I wasn't able to find a guide for tracking new HubSpot v4 forms so here we go.

If you are using legacy forms you can use a guide like this one by Jason Nettles.

The API documentation for the global form events tells us what form events we can listen to.

HubSpot Developer Documentation

Translating this to something GTM compatible was a bit tricky but hopefully this will act as a quick start for you.

We have listeners for the following hs-form-events:

Form Ready - hs-form-event:on-ready

Successful Form Submission - hs-form-event:on-submission:success

Failed Form Submission - hs-form-event:on-submission:failed

Form Navigate - hs-form-event:on-interaction:navigate

You can easily extend this code to add:

Form Navigate Next - hs-form-event:on-interaction:navigate:next

Form Navigate Previous - hs-form-event:on-interaction:navigate:previous

Steps For Implementation

1) Create a custom HTML Tag and add your desired listeners (code below), set it to trigger on all pages.

2) Set up variables to capture any data layer variables you are pushing via the events. For instance we send user-email on a successful form submission

3) Setup triggers for the custom events you want to track e.g. successful form submissions:

4) Setup analytics tags and user-provided data variables as required.

Listener Code - Full example at the bottom

Form Ready Code:

<script>
  window.addEventListener('hs-form-event:on-ready', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;

    window.dataLayer.push({
      'event': 'hubspot-form-ready-v4'
    });
  });
</script>

Successful Form Submission Code:

Note the method for extracting the "email" field value. This is then passed to the GTM data layer and can be used for enhanced conversions.

<script>
  window.addEventListener('hs-form-event:on-submission:success', function (event) {
    var form = HubSpotFormsV4.getFormFromEvent(event);
    //form.getFieldValue allows us to get values of specific fields from the form 
    // based on their field name
    form.getFieldValue('0-1/email').then(function (email) {
      var emailValue = email || '';
      
      //Datalayer push lets us interact with this data in GTM
      // Push all values in one atomic call
      window.dataLayer.push({
        'user-email': emailValue,
        'event': 'hubspot-form-success-v4'
      });
    });
  });
</script>

Failed Form Submission code:

<script>
  window.addEventListener('hs-form-event:on-submission:failed', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;

    window.dataLayer.push({
      'event': 'hubspot-form-failed-v4'
    });
  });
</script>

Form Navigate Code:

<script>
  window.addEventListener('hs-form-event:on-interaction:navigate', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;
    var currentStep = event.detail.currentStep;
    var form = HubSpotFormsV4.getFormFromEvent(event);

    window.dataLayer.push({
      'event': 'hubspot-form-navigate-v4',
    });
  });
</script>

That covers all the main event listeners. You can combine them into a single HTML tag in GTM or you can run them individually. Full code snippets are below.

Remember to test and let me know if you run into any issues!

Full Boiler Plate Code - Put it in a custom HTML tag, trigger on all pages

<script>
  window.addEventListener('hs-form-event:on-ready', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;

    window.dataLayer.push({
      'event': 'hubspot-form-ready-v4'
    });
  });

  window.addEventListener('hs-form-event:on-submission:success', function (event) {
    var form = HubSpotFormsV4.getFormFromEvent(event);
    //form.getFieldValue allows us to get values of specific fields from the form 
    // based on their field name
    form.getFieldValue('0-1/email').then(function (email) {
      var emailValue = email || '';

      //Datalayer push lets us interact with this data in GTM
      // Push all values in one atomic call
      window.dataLayer.push({
        'user-email': emailValue,
        'event': 'hubspot-form-success-v4'
      });
    });
  });

  window.addEventListener('hs-form-event:on-submission:failed', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;

    window.dataLayer.push({
      'event': 'hubspot-form-failed-v4'
    });
  });

  window.addEventListener('hs-form-event:on-interaction:navigate', function (event) {
    var formId = event.detail.formId;
    var instanceId = event.detail.instanceId;
    var currentStep = event.detail.currentStep;
    var form = HubSpotFormsV4.getFormFromEvent(event);

    window.dataLayer.push({
      'event': 'hubspot-form-navigate-v4',
    });
  });
</script>

Example GTM Listener Code based on what I am using with some custom logic:

<script>
//This script handles the key events listeners and can be customised to 
// suit your needs.
  
// Handle the 'on-ready' event.
// This isn't required.
window.addEventListener('hs-form-event:on-ready', function(event) {
  var formId = event.detail.formId;
  var instanceId = event.detail.instanceId;
  //console.log('Form Ready:', formId); 
});

//Handle Successful Form Submission
window.addEventListener('hs-form-event:on-submission:success', function(event) {
  var form = HubSpotFormsV4.getFormFromEvent(event);

  //form.getFieldValue allows us to get values of specific fields from the form 
  // based on their field name
  form.getFieldValue('0-1/email').then(function(email) {
    var emailValue = email || '';
    if (!emailValue) {
      //Useful for debugging
      //Can be commeted out for live
      console.log('Email field is empty!');
    }

    //We use this "Form Type" field to trigger specific events
    //E.g. Form Type == Short Form we trigger the Short Form GA4 Event
    //We nest the get field value call inside the previous call we did to get 
    //the email address
    
    form.getFieldValue('0-1/form_type').then(function(formType) {
      var formTypeValue = formType || '';
      if (!formTypeValue) {
        //In our case this is an issue.
        console.log('Form Type field is empty!');
      }

      //Datalayer push lets us interact with this data in GTM
      // Push all values in one atomic call
      window.dataLayer.push({
        'user-email': emailValue,
        //This is a custom form property and doesn't exist by default
        'form-type': formTypeValue,
        //This is the Custom Event Trigger name you would use in GTM
        'event': 'hubspot-form-success-v4'
      });
    });
  });
});

// Handle failed form submissions if you need to.
window.addEventListener('hs-form-event:on-submission:failed', function(event) {
  var formId = event.detail.formId;
  var instanceId = event.detail.instanceId;
  console.log('Form Submission Failed:', formId); 
});
  
  
// Handle the navigate event
// This is for HubSpot multi-step forms
// Not required for single-step forms
window.addEventListener('hs-form-event:on-interaction:navigate', function(event) {
  var formId = event.detail.formId;
  var instanceId = event.detail.instanceId;
  var currentStep = event.detail.currentStep;
  var form = HubSpotFormsV4.getFormFromEvent(event);

  //Check if our custom field value exists
  //You can remove this check and just do a DL push
    form.getFieldValue("0-1/form_type")
    .then(function (value) {
      if (!value) {
        //If it doesn't just push a plain DL event
          window.dataLayer.push({ 
          'event': 'hubspot-form-nav-v4', 
  });
      } else {
        window.dataLayer.push({
          //If it does push the form type to DL
          'event': 'hubspot-form-nav-v4', 
          "form-type": value,
        });
      }
    }); 
});
</script>