Hooking into analytics.js send with customTask

While Google Tag Manager provides many customization options in its Google Analytics tag template, it does not, by default, allow for the insertion of instructions between the create and send methods of analytics.js. Not having direct access to the ga command queue can sometimes lead to challenges when trying to access the Google Analytics tracker and model objects before a tag is sent.

Solution

The customTask is a no-operation function that executes first in the sequence of analytics.js tasks. It is designed to be overridden by developers to insert custom instructions that are executed when a tag is sent. In Google Tag Manager it is possible to override the customTask by setting the field on the tag template (Fields to Set) to a Custom JavaScript Variable that returns a function.

Usage Examples

Collecting the Client ID in a custom dimension

Inserting the Client ID value from the tracker object into a custom dimension is a common requirement for users of Google Analytics. It enables user-level reporting outside of the User Explorer report and facilitates online-to-offline integrations by exporting an un-hashed Client ID value to BigQuery. On the surface, this task may involve some simple JavaScript to extract and assign the clientId field value to a custom dimension. However, it is not possible to send the custom dimension with this value for the first pageview of a new user, which leads to an extra hit being required. To side-step this extra hit, set the customTask field in your Universal Analytics tag to a Custom JavaScript Variable with the function below:

function () {
  return function () {
    try {
      // Retrieve all trackers
      var trackers = ga.getAll();
      trackers.forEach(function(tracker) {
        // Get the Client ID
        var cid = tracker.get('clientId');
        // Set the Client ID in a custom dimension at index 1
        tracker.set('dimension1', cid);
      });
    } catch (e) {}
  }
}

Adding to subsequent tasks

Because customTask is executed first in the tasks sequence it provides an opportunity to override other subsequent tasks. You may replace tasks with your own custom functions or augment the existing functionality by chaining your custom functions to execute before or after an existing task. In the example below, we retrieve the hit payload and send it to a custom endpoint while still retaining the original sendHitTask functionality:

function () {
  return function () {
    try {
      // Get the default GTM tracker by name
      var tracker = ga.getByName('gtm1');
      // Grab a reference to the default sendHitTask function
      var originalSendHitTask = tracker.get('sendHitTask');
      // Override the sendHitTask
      tracker.set('sendHitTask', function(model) {
        // Execute the original sendHitTask
        originalSendHitTask(model);
        // Send the hit payload to a Cloud Function for processing
        var xhr = new XMLHttpRequest();
        xhr.open('POST', 'https://us-central1-starlit-granite-545.cloudfunctions.net/ga-proxy-collect', true);
        xhr.send(model.get('hitPayload'));
      });
    } catch (e) {}
  }
}

Manipulating the hit payload

Having access to the hit payload means it can also be manipulated (with caution). Customizations to the GA tag are configured in the tag template but some corner cases require manual alterations to the payload. An example of this is serving the same mobile web pages for both browsers and webviews in native apps. Most of the app tracking being native it’s sometimes desirable to send screenview type hits from a web container in order to populate a Google Analytics app view. This is possible by configuring the appropriate mobile app fields in the tag template and replacing the hit type:

function () {
  return function () {
    try {
      // Get the default GTM tracker by name
      var tracker = ga.getByName('gtm1');
      // Grab a reference to the default sendHitTask function
      var originalSendHitTask = tracker.get('sendHitTask');
      // Override the sendHitTask
      tracker.set('sendHitTask', function(model) {
        // Replace pageview with screenview in the payload
        var newHitPayload = model.get('hitPayload').replace(/t=pageview/, 't=screenview');
        // Set the new payload value
        model.set('hitPayload', newHitPayload, true);
        // Execute the original sendHitTask
        originalSendHitTask(model);
      });
    } catch (e) {}
  }
}

Aborting a hit

Similarly to exceptions in triggers and other validation-related tasks, you can add custom filters to the customTask. An example would be overriding the displayFeaturesTask to null if the user didn't provide consent. Another example could be enforcing a Do Not Track initiative on the client-side by aborting the hit:

function () {
  return function () {
    // Abort hit when DoNotTrack is configured in the browser
    if (window.navigator && 1 == window.navigator.doNotTrack) {
      throw "abort";
    }
  }
}
Was this helpful?
How can we improve it?