How to Track Marketo Forms in Google Analytics Using Google Tag Manager

Marketo is one of the most popular marketing automation platforms, particularly among enterprise businesses.

Using Google Tag Manager, it is possible to set up tracking of Marketo forms globally across your site, letting you trigger not only Google Analytics events, but other analytics and pixels as well.

Marketo Form2 Library

Marketo uses a JavaScript library called Forms 2.0 to load forms dynamically on the page.

<script src="//app-sjqe.marketo.com/js/forms2/js/forms2.js"></script>
<form id="mktoForm_621"></form>
<script>MktoForms2.loadForm("//app-sjqe.marketo.com", "718-GIV-198", 621);</script>

This is a common Marketo form embed code. It is three lines of code that operate in the following way:

  1. The first line loads the standard forms.js library
  2. An empty form element with a specific ID. The element ID,  mktoForm_621, specifies the ID of the form to be loaded (621)
  3. Finally, the invocation of the loadForm function, specifies the account subdomain, the account ID and the form number (621). This tells the forms.js library to find the form element on the page with the specified ID and populate it with the form HTML.

The forms2.js library provides a common API for interacting with Marketo forms, which means it’s possible to have a single tagging setup that will track all the forms on your site, including landing pages you create within Marketo.

Integrating Form Events with Google Tag Manager

First, create a custom HTML tag which we’ll call HTML – Marketo Form Listener. This is our “listener” script that will push the Marketo events to the data layer where we can set up triggers in the normal way.

In this blog post and included container template, the listener script is self-contained in its own tag, but if you have other custom scripts that load on every page, you could easily place this code in that too. The key is that this tag needs to be loaded on either DOM Ready or Window Loaded so that the Marketo script has loaded and we have an API to interact with.

You could also load the script conditionally on just the pages or parts of the site that have Marketo forms, however the script is lightweight enough that I prefer to load globally. That way, if a new form is added to a different page the script will automatically track it.

The following is my standard boilerplate GTM tag code – it encapsulates the code, preventing conflicts with global variables and hides errors from the console unless you are running the container in debug mode (you will need to enable to Debug Mode variable for this).

The var dataLayer statement creates a shortcut variable to the dataLayer object and means we only have to change the name in once place if our container uses a different name for the data layer.

<script>
  (function(document, window, undefined){

    var dataLayer = window.dataLayer;

    try {
      
      // code goes here
      
    } catch(err) {
      if({{Debug Mode}}){
        console.log(err);
      }
    }
  })(document, window);
</script>

For brevity, assume the subsequent code will be placed where it says ‘code goes here’.

Marketo Forms are loaded asynchronously, and forms have to be loaded before they can be interacted with.

Use the MktoForms2.whenReady function to register a callback function that will accept one argument – form. When the form is loaded, the callback will be triggered and a form object will be passed to the registered handler that allows us to interact with the specific form instance. Marketo lets you have multiple forms on a page, so our code has to be written in such a way that any references to the form objects are encapsulated within the whenReady callback to ensure we are only interacting with one form instance at a time.

We also wrap the entire code block in a conditional statement so that if the Marketo library isn’t loaded

if(window.MktoForms2){
  window.MktoForms2.whenReady(function(form){
    var form_id = form.getId();
    var $form = form.getFormElem();
    //...
  });
}

Each form object has a number of methods we can interact with – for starters we are going to get the form ID and a jQuery object containing the form element. Be sure to declare the variables with var as we need to be careful with our variable scoping to ensure we only interact with a single form at at a time.

Note: while ECMAScript 6 has the ‘let’ statement for setting block-scoped variables, I generally try to use ECMAScript 5 for GTM implementation for cross-browser compatibility. 

Next, let’s add our event callbacks.

Everyone has their own practices they follow when it comes to naming events in GTM. I try to follow the pattern set out by the built-in video tag where events are namespaced by the type of event, followed by the specific event name, for example:

marketo.loaded
marketo.submit
marketo.success

Along with each event there will be various properties pushed to the data layer, all following the same naming convention, e.g.

marketo.form_id 

The marketo.loaded event is triggered immediately, as the form will have been loaded as soon as this code is run.

We register callbacks for onSubmit and onSuccess. Submit is called on any form submission while success is only called when a form is validated and the submission complete. The callback function for Success is given two arguments – the first is a plain JS object containing the form field values, the second is the URL that the user will be redirected to after submission.

if(window.MktoForms2){
  window.MktoForms2.whenReady(function(form){
    var form_id = form.getId();
    var $form = form.getFormElem();
    
    dataLayer.push({
      'event': 'marketo.loaded',
      'marketo.form_id': form_id
    });
    
    form.onSubmit(function(){
      dataLayer.push({
        'event': 'marketo.submit',
        'marketo.form_id': form_id,
      });
    });
    
    form.onSuccess(function(values, followUpUrl){
      dataLayer.push({
        'event': 'marketo.success',
        'marketo.form_id': form_id,
        'marketo.form_values': values,
        'marketo.follow_up_url': followUpUrl
      });

      if({{Debug Mode}}){
        console.log(values);
        return false;
      } else {
        return true;
      }
    });
              
  });
}

Testing and Debugging Marketo Tagging

Testing Marketo form tagging can be difficult because once a form is submitted, you are either redirected or the page refreshes. While you can persist the console on your browser to see various calls and messages, you can’t easily see the GTM debug bar with the dataLayer state once you submit.

To solve this, we add an additional block of code within the onSuccess callback that checks whether the container is in debug mode. If it is, it logs the values of the form fields to the console and returns false, which tells the forms2.js library not to redirect the user. This will only occur for users who are in preview mode within GTM, so there is no danger of this behavior being seen by regular users. On submit, the form will hang, typically with a “please wait” message in the button. Within the GTM debug bar, we will see the following:

Google Tag Manager Marketo integration. Prevent form from submitted on success in order to inspect data layer state.
GTM Data Layer state after Marketo form submission. 

Trigger Event When Marketo Form in Viewport

I have always found it useful to track when a call to action form scrolls into the user’s viewport. Marketo forms are often placed at the bottom of long pieces of content and it’s helpful to differentiate between users who saw the form and didn’t fill it out versus those who just didn’t make it to the form to begin with.

GTM added an element visibility trigger in late 2017, and while this is an extremely useful trigger, it requires a CSS selector or element ID, making it not particularly flexible for dynamically loaded elements.

Instead, I use my utility library: a set of functions following a simplified underscorejs pattern that I’ve used as a backbone for my tagging implementations for a number of years.

Within this utility library is a function called onElementAppear, which takes an HTML element, a “bounds” integer (a padding around an element that will trigger the in-view event) and a callback function.

(function(document, window, undefined){
  
  var _ = {{Utility Library}};
  
  var dataLayer = window.dataLayer;
      
  try {
          
    if(window.MktoForms2){
      window.MktoForms2.whenReady(function(form){
        var form_id = form.getId();
        var $form = form.getFormElem();
        
        dataLayer.push({
          'event': 'marketo.loaded',
          'marketo.form_id': form_id
        });
                  
        _.onElementAppear($form[0], 100, function(){
          dataLayer.push({
            'event': 'marketo.in_view',
            'marketo.form_id': form_id
          })
        });          

        form.onSubmit(function(){
          dataLayer.push({
            'event': 'marketo.submit',
            'marketo.form_id': form_id,
          });
        });

        form.onSuccess(function(values, followUpUrl){
          dataLayer.push({
            'event': 'marketo.success',
            'marketo.form_id': form_id,
            'marketo.form_values': values,
            'marketo.follow_up_url': followUpUrl
          });

          if({{Debug Mode}}){
            console.log(values);
            return false;
          } else {
            return true;
          }
        });
                  
      });
    }
    
  } catch(err) {
    if({{Debug Mode}}){
      console.log(err);
    }
  }
})(document, window);

The actual HTML element within the jQuery object can be accessed as the first element of an array $form[0].

Configuring Triggers and Events

Let’s create a Google Analytics events for form visibility and successful submission.

For starters, we’re going to create two triggers for form visible and form success respectively:

Next, we’re going to create variables for the event action and label. I prefer to limit the number of overall tags in my container and use dynamic containers wherever possible to modify values within my tag. In this case, that means a single GA Event tag that will be triggered on both in_view and success events, and will use variables to set the necessary values within the tag.

The Form Action variable uses the event name to set a readable action label for the GA event.
The Form ID variable gets the Marketo Form ID from the data layer.

Finally, let’s bring it all together in a Google Analytics even tag:

Either event will trigger the tag, the action being dynamically set based on the type of event and the label being set to the ID of the form.

The result, in the Google Analytics report, will be a clean set of global “form visible” and “form submission” events which can be optionally used for goal configuration.

Alternatively, you can drill down to a particular form by ID, and see the interactions just for that form.

Download Full Solution

Instructions:

Import container JSON as a new Workspace for testing, and select the option to “merge” container with existing

Container Contains:

Tags
GA – Marketo Form
HTML – Marketo Form Listener
Variables
GA CONFIG (placeholder for Google Analytics configuration – replace with your own)
Marketo Form Action
Marketo Form ID
Utility Library
Triggers
Marketo – Form In View
Marketo – Form Success

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.