Scripting Google Tag Manager Configuration

Written by Tom Wilkins
Sat, 18 Apr 2020

Speed up configuration changes with these tips.

Scripting Google Tag Manager Configuration

Google Tag Manager is a useful tool for marketers, empowering them to add custom tags and HTML to their websites without going through a developer.

This approach has pros and cons - things like tracking tags can be implemented quickly by the people that understand their purpose, but because things can be added so easily, the configuration can quickly become unmanageable.

I recently looked at Google Tag Manager container that required housekeeping, and found that while the UI is easy to use, some tasks became repetitive and slow. I then noticed a cool feature of Google Tag Manager - the fact that you can export/import configuration as JSON.

The TypeScript interfaces below demonstrate the structure the exported JSON will follow:

export interface GoogleTagManagerConfig {
exportFormatVersion: number;
exportTime: string;
containerVersion: ContainerVersion;
}
export interface ContainerVersion {
path: string;
accountId: string;
containerId: string;
containerVersionId: string;
container: Container;
tag: Tag[];
trigger: Trigger[];
variable: Variable[];
builtInVariable: BuiltInVariable[];
fingerprint: string;
tagManagerUrl: string;
}
export interface Container {
path: string;
accountId: string;
containerId: string;
name: string;
publicId: string;
usageContext: string[];
fingerprint: string;
tagManagerUrl: string;
}

export interface Parameter {
type: string;
key: string;
value: string;
}

export interface MonitoringMetadata {
type: string;
}

export interface Tag {
accountId: string;
containerId: string;
tagId: string;
name: string;
type: string;
parameter: Parameter[];
fingerprint: string;
firingTriggerId: string[];
blockingTriggerId: string[];
tagFiringOption: string;
monitoringMetadata: MonitoringMetadata;
}

export interface Parameter2 {
type: string;
key: string;
value: string;
}

export interface Filter {
type: string;
parameter: Parameter2[];
}

export interface Trigger {
accountId: string;
containerId: string;
triggerId: string;
name: string;
type: string;
filter: Filter[];
fingerprint: string;
}

export interface Parameter3 {
type: string;
key: string;
value: string;
}

export interface Variable {
accountId: string;
containerId: string;
variableId: string;
name: string;
type: string;
parameter: Parameter3[];
fingerprint: string;
}

export interface BuiltInVariable {
accountId: string;
containerId: string;
type: string;
name: string;
}

Now we have this, we can use a simple Node script to manipulate the configuration to speed up our changes, and it will be more fun.

JSON is incredibly easy to work with in Node, as JSON is valid JavaScript, we can simply save the JSON in our working directory and require it in, like so:

const gtmConfig = require('./path-to-gtm-config.json');

And then it's there as a JavaScript object, ready to work with!

What can we do from here?

Delete unwanted tags

One specific problem I was trying to solve was a clean up of Google Analytics events. In the container, an Analytics tag had been added for each individual event, and was a real nightmare to maintain.

I was looking to replace these with a single tag, using the dataLayer to dynamically populate the eventCategory, eventAction and eventLabel.

Fortunately, each of these events had been named consistently, so the first thing I can do is find all tags following the naming convention, and delete them.

gtmConfig.containerVersion.tag = gtmConfig.containerVersion.tag.filter(({ name }) => {
const isEvent = name.endsWith('-event');
if (isEvent) {
console.log(name)
}
return !isEvent
});

Here, I'm using the filter array method to filter out tags where the name ends with '-event'. The name is all I need from the tag object, so I'm using destructuring in the function parameter to grab it.

I create a variable, isEvent to check the tag name against my condition. If it is an event, it's something I'll be filtering out so I'm then logging the name to the console to help me sense check and ensure nothing unexpected will be removed from the configuration.

It's then a simple case of using the ! operator with the isEvent variable in the return statement as everything I want to keep in the configuration will be tags that don't pass this condition.

You might notice that I'm mutating the original array in this code. This is normally something I'd avoid, but in this instance the original configuration is in a file that I won't be affecting through my code.

Delete unused triggers

Now that we've removed a bunch of tags, we probably now have several unused triggers. In Google Tag Manager, a trigger determines when a tag should fire, or when a tag should not fire. Triggers can often be created specifically for a particular tag, so it would make sense that deleting tags will leave unused triggers.

This is where we can get more intelligent with our scripting. Because the entire configuration is present in the gtmConfig object, we can determine which triggers are used and which are not.

Each "tag" object can contain the properties firingTriggerId and/or blockingTriggerId, which if present will be an array of trigger ids that correspond to our triggers - so I can check to see if any of the trigger ids are referenced.

gtmConfig.containerVersion.trigger = gtmConfig.containerVersion.trigger.filter(({ triggerId, name }) => {
const isTriggerUsed = gtmConfig.containerVersion.tag.some(referencesTriggerId(triggerId));
if (!isTriggerUsed) {
console.log(name);
}
return isTriggerUsed
});

function referencesTriggerId (id) {
return tag => {
if (tag.firingTriggerId && tag.firingTriggerId.includes(id)) return true;
if (tag.blockingTriggerId && tag.blockingTriggerId.includes(id)) return true;
return false;
}
}

Again, I'm using a filter method here and filtering out unused triggers. From the trigger I'm object, I'm destructuring the triggerId and the name properties.

I'm then looking in the tag array to determine if any tags reference the trigger id as a firing trigger or a blocking trigger. I can do this with the some method, which will return true if any item in the array satisfies my condition.

My condition is determined by the referencesTriggerId function, which is a function factory using the triggerId to construct a bespoke check for each triggerId. This function receives the triggerId, and returns a function that determines if that id is a firing trigger or a blocking trigger.

It is the function returned by referencesTriggerId that is passed to the some method to check this trigger id against each tag in the configuration.

Again, if it's a trigger that will be filtered out by the script, it will be logged to the console for sense-checking. This is why the name was tag from the trigger as well as the id. The ids aren't going to mean anything to me looking at them!

Writing to a new configuration file

Now that I've made my changes, I can save them to a new JSON file ready to import into Google Tag Manager. I'll do this with the fs module which is native to Node.js.

const fs = require('fs');

fs.writeFileSync('./processed-gtm-config.json', JSON.stringify(gtmConfig));

This will write the new configuration into my working directory. Importing the file into Google Tag Manager is then the acid test, it will stop you importing if anything has gone wrong!

Further reading

If you would like to learn more about the techniques used here, I have posts around Array Methods and Destructuring.

It's also possible to edit Google Tag Manager configuration directly using the Google Tag Manager API. This is going to more complicated to use than my examples above (dealing with authentication for example), but it's going to be far more suitable if you want to build you own application to manage GTM on an ongoing basis.

Thank you for reading

You have reached the end of this blog post. If you have found it useful, feel free to share it on Twitter using the button below.

Tags: TIL, Node.js, JavaScript, Blog, Google Tag Manager