Home Java Custom Templates in GTM: A Case Study

Custom Templates in GTM: A Case Study

by admin

At the end of May, Google introduced a new feature of Google Tag Manager (GTM), called Custom Templates.Let’s find out what this feature is for, how to use it, and what the differences are between it and HTML tags and JavaScript variables.
As an example, let’s look at creating a Custom Template for the "VKontakte" dynamic retargeting pixel and further configuring GTM tags through it.
Custom Templates in GTM: A Case Study
In simple words about Custom Templates
Creating Custom Template
– Info tab
• Fields tab
• Code tab
* Permissions tab
Customizing and testing a tag on a created Custom Template
– Pageview
• AddToCart
– Checking the workout

Custom Templates in layman’s terms

Custom Templates are templates that users can use to create new tags or variables.GTM has ready-made templates (in the Featured or Recommended section), such as Google Analytics tag, Google Optimize, and others. Now we can supplement them with our own templates. Once created, they will appear in the Custom tab:
Custom Templates in GTM: A Case Study
The key difference with HTML tags and JS variables is that when the user creates a tag or variable from a ready-made template, it does not interact with JS code.
The code for the custom template is written by the developer or web analyst at the creation stage. Then, when you create a tag or variable using the custom template, all interaction is done through the interface (which is also configured when you create the custom template):
Custom Templates in GTM: A Case Study
Accordingly, compared to writing an HTML tag or JS variable, customizing a tag or variable by a custom template becomes an order of magnitude easier because it requires no knowledge or skill with JavaScript.
Another big plus of custom templates is that there is an order of magnitude less chance of "putting" the site down due to an error in the JS code of the tag.
In our example, to configure the VKontakte dynamic retargeting tag, you no longer need to contact the developers – you can configure everything yourself, even the transfer of product data (with the advanced e-commerce Google set up on the site), having only experience with GTM.

Create Custom Template

Since we are creating a tag template, we need to go to the Templates section and click the New button in the Tag Templates section.
Custom Templates in GTM: A Case Study
After that, the Template Editor opens:
Custom Templates in GTM: A Case Study
On the left side of the editor is the settings window, on the right side is the preview window and the console. There are four tabs in the settings window, which are necessary for creating and working with the template.

Info tab

This tab fills in information about the tag: name, description, icon. This is the information we will see when we create a new tag in the list of templates:
Custom Templates in GTM: A Case Study
For the tag icon has requirements: PNG, JPEG or GIF format, a minimum resolution of 64×64 pixels and a maximum size of 50 KB.

Fields tab

In this section we create the interface of our template. The interface is the various fields and forms that the user interacts with during the creation of a new tag or variable.
The information that the user entered when creating the tag using the interface is then used in the template code.
To add a new element, click the Add Field button. The window for selecting the element type appears:
Custom Templates in GTM: A Case Study
GTM allows you to select the following types of interface elements :

  • text field ;
  • drop-down menu ;
  • checkbox ;
  • switches ;
  • simple table , you can edit each cell here.
  • extended table , you can edit only a string, this type of table is convenient for creating a dictionary from "key-value" pairs;
  • group of elements , allows you to group multiple types into a group;
  • shortcut is used to add text to the interface, it does not require the user to enter any data.

After adding an element, you have to give it a clear name, which will then be used in the code. This is a kind of variable name – it should be understandable and reveal the essence of the created interface element. For example, the name "ID" doesn’t say anything in particular, but the name "pixelIDs" already shows that this item stores the ID of the pixels entered by the user:
Custom Templates in GTM: A Case Study
Next you need to go to the settings of each item and activate the necessary properties.
Different situations require different interface element properties, so they are all hidden by default and we need to activate the ones we need now:
Custom Templates in GTM: A Case Study
The available properties differ from element type to element type, I will point out the most frequently used, which almost all elements have :
1. Display name This is the name that the user will see in the interface when creating the tag :
Custom Templates in GTM: A Case Study
2. Example value This is a hint to the user about what values to enter in the field :
Custom Templates in GTM: A Case Study
3. Reference text This is the text that the user will see if he hovers over the element’s help icon :
Custom Templates in GTM: A Case Study
4. Data verification rules In this property, you can set certain rules for checking the data entered by the user in the field and, if necessary, the error text that will appear when you try to save a tag with data that failed to pass the check.
For example, you can specify that the field must be filled in. Second example: you want to get an email address from the user, then you can make a check that the data entered by the user must match a regular expression *@.*..*
Custom Templates in GTM: A Case Study
To specify the error text that will appear if the entered data do not match the validation rules, you must activate the advanced rule settings :
Custom Templates in GTM: A Case Study
Custom Templates in GTM: A Case Study
Also in the advanced settings you can specify the conditions under which this rule is activated (the Enable Condition field).
5. Terms of Activation These are the conditions under which the user will have this interface element.
For example, in order not to overload the template with interface elements, you can make the necessary elements appear when the checkbox is checked. That is, let’s assume that if the user wants to set up the transfer of product data to the pixel (this is possible when Google’s advanced e-commerce is set up on the site), he checks "Use dataLayer to transfer product data", and after checking the box the elements necessary to set up such a transfer appear in the interface of the tag. If the checkbox is unchecked, there are no elements in the interface.
Custom Templates in GTM: A Case Study
Note that here you need to specify the name of the element, which should have been assigned to it immediately after adding it.
As settings are added and the interface is created, all changes can be viewed and tested immediately in the preview window:
Custom Templates in GTM: A Case Study

Code tab

This tab is a code editor.
GTM Custom Templates code is written in stripped-down ES6 JavaScript and runs in an isolated environment where all communication with the global data (i.e. the page itself) is done through the API. It doesn’t have global objects such as window or document and, consequently, neither do its regular methods. For example, constructors (new Object and the like), setTimeout, parseInt, delete, etc. – none of these things will work in Custom Template.
But there’s an API for all of this. And, therefore, writing code for Custom Template should start with defining the APIs we’ll use in our code :

//used API//method to get the value of a global variableconst copyFromWindow = require('copyFromWindow');//method for setting the global variable valueconst setInWindow = require('setInWindow');//method for installing a third-party script on the pageconst injectScript = require('injectScript');//method for calling a global functionconstInWindow = require('callInWindow');//method to convert the data obtained from the extended table in the interface into a standard objectconstTableMap = require('makeTableMap');//method for working with the URL of the pageconst getUrl = require('getUrl');//method for getting request parametersconst getQueryParameters = require('getQueryParameters');//method of transformation into an integerconst makeInteger = require('makeInteger');//transformation method into a stringconstString = require('makeString');//the method is an analogue of setTimeoutconstLater = require('callLater');//method analogue of console.logconst logToConsole = require('logToConsole');

A complete list of APIs with detailed documentation can be found at Google Help For Developers
I’ll show you how to work with API by examples:

Description of action Classic JS Custom Template API
Console output console.log(‘Hi’); logToConsole(‘Hi’);
Set a timer setTimeout(function, 100); callLater(function);
Conversion to string String(1234); makeString(1234);
Conversion to an integer parseInt(‘1234’, 10); makeInteger(‘1234’);
Page host window.location.hostname getUrl(‘host’);

As you can see from the table of examples, once the API is defined, it should be used instead of the standard JS constructs.
Once we’ve defined the API, it’s desirable to define an object with the settings that the user has entered. This can also be done afterwards, e.g. during an executable, by asking for the necessary data from the user’s settings. But if we define the settings object from the beginning, it’s easier to work with the code later on because all the user settings are stored in a separate object.
To get data from an interface element, you have to use the data construct.
{{item name}}

//object with user settingsconst settings = {// event nameevent: data.event, //pixelID (pixelIDs)pixelIDs: data.pixelIDs, //ID of the price list (if using 1 price list)priceListId: data.priceListId, //will we use several price lists?fewPriceLists: data.fewPriceLists, //ID of the price lists (if several price lists are used)priceListIds: data.priceListIds === undefined ? data.priceListIds : makeTableMap(data.priceListIds, 'hostname', 'priceListId'), //use ecommerce to pass the products?ecommerceUse: data.ecommerceUse, //ecommerce object for the eventeventEcommerce: data.eventEcommerce, //site search query parametersiteSearchQueryParam: data.siteSearchQueryParam};

Note : if undefined is passed to the makeTableMap method, it will cause a script error, so I use a ternary operator construct (a shortened if-else construct) to filter out such scripts.
About the makeTableMap method. If an extended table is used in the interface, the data in it is stored like this :

['key': 'k1', 'value': 'v1'}, 'key': 'k2', 'value': 'v2'}]

After processing with the makeTableMap method, the data becomes a normal object with key-value pairs:

{'k1': 'v1', 'k2': 'v2'}

Another requirement for Custom Template code: if the tag succeeds, you must call the data.gtmOnSuccess() method, and if an error occurs, call the data.gtmOnFailure() method.
For example, in my code, the data.gtmOnSuccess() method is called after a successful request, and the data.gtmOnFailure() method is called when the external VK openapi.js script fails to load on the page.
After defining the API and defining the object with the settings, you can start writing the algorithm for pixel processing.
The main thing to remember here is this :
– If you want to get a global variable, you use the copyFromWindow API method.

copyFromWindow('VK');//VK is the name of the global variable, the value of which we want to get

– If you want to set a global variable, use the setInWindow API method.

setInWindow('openapiInject', 1);//openapiInject is the name of the global variable, the value of which we set//1 - value, which we're going to assign to the global variable.

– If you want to run a global function, use the callInWindow API method.

callInWindow('VK.Retargeting.Init', p);//VK.Retargeting.Init is the name of the global function to call//p - parameter, which we pass to the function to be executed

– If you want to add an external script to the page – use method API injectScript.

injectScript('https://vk.com/js/api/openapi.js?159', pixel.setVkAsyncInit(), data.gtmOnFailure, 'vkPixel');//https://vk.com/js/api/openapi.js?159 - address of the script, which should be embedded into the page//pixel.setVkAsyncInit() - the function to be executed in case of successful script loading//data.gtmOnFailure - the function which is executed if the script fails to load.//vkPixel - optional string, which indicates that the provided URL should be cached. If you set this value, only one script element will be created for JavaScript request

– If you want to get URL (or part of it) – use API getUrl method.

getUrl('host');//host is the parameter of the URL we're requesting. Available parameters except host: protocol, port, path, extension, fragment, query.

As I wrote above, Custom Template supports JS ES6. It’s preferable to use this syntax because it makes code shorter and more readable, and JS work more predictable and similar to other programming languages.
Learn more about JS ES6 syntax The main things desirable to use are arrow functions and const and let variable declarations instead of var.
A variable declared with const is a constant whose value cannot be changed.
A variable declared via let differs from one declared via var as follows :

  • let is not added to the global window object;
  • The visibility of the let variable is limited to the declaration block;
  • Variables declared with let cannot be re-declared.

Arrow functions are abbreviations of conventional functions :

//1. Standard functionconst func1 = function() {return 'test';}//Analogous to it arrow functionconst func1 = () => 'test';//2. Standard functionconst func2 = function(arg) {if (arg > 0) return 'plus';{ else return 'minus';}//Analogous to it, the arrow functionconst func2 = arg => {if (arg > 0) return 'plus';else return 'minus';}//3. Standard functionconst func3 = function(arg1, arg2){if (arg1 > arg2) return arg1;{ else return arg2;}//Analogous to it, the arrow functionconst func3 = (arg1, arg2) => {if (arg1 > arg2) return arg1;else return arg2;}

Now that we understand how to use the Custom Template API, we can write the code for the tag using JavaScript ES6 syntax.
My code contains methods for launching the pixel, installing VK openapi.js, getting product data from the dataLayer (with Google Advanced E-commerce set up on the site), processing that data to make it look the way it needs to be sent to the VK retargeting pixel, and a method for sending the event.
The pixel start method supports three work scenarios :

  1. The pixel runs on a page that lacks openapi.js.
  2. The pixel runs on a page that has openapi.js, but it hasn’t been loaded yet.
  3. The pixel runs on the page with openapi.js loaded.

Full code of my custom GTM template for VKontakte dynamic retargeting pixel

//apiconst copyFromWindow = require('copyFromWindow');const setInWindow = require('setInWindow');const injectScript = require('injectScript');const callInWindow = require('callInWindow');const makeTableMap = require('makeTableMap');const getUrl = require('getUrl');const getQueryParameters = require('getQueryParameters');const makeInteger = require('makeInteger');const makeString = require('makeString');const callLater = require('callLater');//object with user settingsconst settings = {event: data.event, pixelIDs: data.pixelIDs, priceListId: data.priceListId, fewPriceLists: data.fewPriceLists, priceListIds: data.priceListIds === undefined ? data.priceListIds : makeTableMap(data.priceListIds, 'hostname', 'priceListId'), ecommerceUse: data.ecommerceUse, eventEcommerce: data.eventEcommerce, siteSearchQueryParam: data.siteSearchQueryParam};//main object with pixel methods and propertiesconst pixel = {//method for determining the page hostgetPageHostname: () => getUrl('host'), //method to get a global VK objectgetVK: () => copyFromWindow('VK'), //method to set global VK callbacksetVkAsyncInit: () => {setInWindow('vkAsyncInit', pixel.sendEvent);}, //method to determine the user's search query on the sitegetSiteSearchPhrase: () => {if (settings.event === 'view_search') return getQueryParameters(settings.siteSearchQueryParam);else return undefined;}, //method of specifying event parameters for a pixelgetEventParams: (products, currencyCode, revenue) => {let eventParamsClean= {};let eventParams = {products: eventProducts.getProductParams(products), category_ids: eventProducts.getCategoryString(products), currency_code: currencyCode, total_price: eventProducts.getTotalPrice(products, revenue), search_string: pixel.getSiteSearchPhrase()};if (eventParams.products !== undefined) eventParamsClean.products = eventParams.products;if (eventParams.category_ids !== undefined) eventParamsClean.category_ids = eventParams.category_ids;if (eventParams.currency_code !== undefined) eventParamsClean.currency_code = eventParams.currency_code;if (eventParams.total_price !== undefined) eventParamsClean.total_price = eventParams.total_price;if (eventParams.search_string !== undefined) eventParamsClean.search_string = eventParams.search_string;return eventParamsClean;}, //price list selection methodgetPriceListId: hostname => {if (settings.fewPriceLists) return settings.priceListIds[hostname];else return settings.priceListId;}, //initialization method openapi.jsopenapiInit: () => {injectScript('https://vk.com/js/api/openapi.js?159 ', pixel.setVkAsyncInit(), data.gtmOnFailure, 'vkPixel');setInWindow('openapiInject', 1);}, //method of sendingthe sendEvent event: () => {if (settings.event === 'hit') {settings.pixelIDs.split(', ').forEach(p => {callInWindow('VK.Retargeting.Init', p);callInWindow('VK.Retargeting.Hit');});} else {const pricelist = pixel.getPriceListId(pixel.getPageHostname());const name = settings.event;let products = [];if(settings.ecommerceUse) products = name === 'view_home' || name === 'view_category' || name === 'view_search' || name === 'view_other' ? settings.eventEcommerce : settings.eventEcommerce.products;else products = undefined;const currencyCode = settings.ecommerceUse ? settings.eventEcommerce.currencyCode : undefined;const revenue = (settings.ecommerceUse name === 'purchase') ? settings.eventEcommerce.actionField.revenue : undefined;const eventParams = settings.ecommerceUse ? pixel.getEventParams(products, currencyCode, revenue) : undefined;settings.pixelIDs.split(', ').forEach(p => {callInWindow('VK.Retargeting.Init', p);callInWindow('VK.Retargeting.ProductEvent', pricelist, name, eventParams);});}, //pixel launch methodstart: () = {if (pixel.getVK() === undefinedcopyFromWindow('openapiInject') !== 1) {pixel.openapiInit();data.gtmOnSuccess();} else if (pixel.getVK() === undefined copyFromWindow('openapiInject') === 1) {if (pixel.count < 50) {callLater(pixel.start);pixel.count++;} else return;} else {pixel.sendEvent();data.gtmOnSuccess();}, //try countcount: 0};//object with methods of processing the array of event productsconst eventProducts = {//method to determine the products object parameters for a pixelgetProductParams: products => {let arr = [];products.forEach(i => {let productParamsClean = {};let productParams = {id: makeString(i.id), group_id: makeString(i.brand), price: makeInteger(i.price * 100) / 100};if (productParams.id !== 'undefined') productParamsClean.id = productParams.id;if (productParams.group_id !== 'undefined') productParamsClean.group_id = productParams.group_id;if (productParams.price !== 0) productParamsClean.price = productParams.price;arr.push(productParamsClean);});return arr;}, //the method gathers product categories into a string of the 'a, b, c' type, checking if each category is uniquegetCategoryString: products => {let categoryId = '';let check = [];products.forEach(i => {if(check.indexOf(i.category) === -1) {check.push(i.category);categoryId += ', ' + i.category;});return categoryId.slice(1);}, //method of determining the total cost of goodsgetTotalPrice: (products, revenue) => {let sumPrice = 0;if (revenue !== undefined ) return makeInteger(revenue * 100) / 100;else {products.forEach(i => {if (i.hasOwnProperty('quantity')) sumPrice += (makeInteger(i.price * 100) / 100) * makeInteger(i.quantity);else sumPrice += makeInteger(i.price * 100) / 100;});return sumPrice;};//starting the pixelpixel.start();

The Permissions tab

After you’ve written the tag code, the final step is to give permissions to interact with the page’s global data. This is done just on the Permissions tab.
Since the code is executed in an isolated environment and interacts with global data via API, we need to manually specify the permissions for each API method (where necessary).
This is done in order to be as deliberate as possible when dealing with global page data, and thus minimize the chances of "putting" the site a bug in the code of our template.
For the API methods used in my code, three types of permissions must be issued :
Custom Templates in GTM: A Case Study
1. Accesses Global Variables – read, write, execute access to the global variables that are used in our code. Variables need to be added manually and for each of them we need to specify what we allow them to do.
Custom Templates in GTM: A Case Study
For example, the VK variable can only be read, vkAsyncInit can be read and overridden, and the VK.Retargeting.Hit method can only be executed.
2. Reads URL Here you need to specify which parts of the URL are allowed to receive. I allow to get any part of the URL:
Custom Templates in GTM: A Case Study
But you can specify some specific ones if you want :
Custom Templates in GTM: A Case Study
3. Injects Scripts This is where you need to specify the addresses from which you can load external scripts. My code loads only one script from VK openapi.js, that’s where I put the address:
Custom Templates in GTM: A Case Study
That’s it, the Custom Template setup is done, you can save the template and move on to testing.

Customizing and testing the tag on the created Custom Template

As an example, let’s create two dynamic VKontakte retargeting tags using the created Custom Template: pageview and addToCart.


Go to the desired GTM container, create a new tag, choose VK Pixel tag type in the Custom section:
Custom Templates in GTM: A Case Study
Fill in the tag name, choose Hit as the tracked event (it’s a standard pageview), put a comma in the "Pixel ID" field with the ID of the two pixels where data will be sent, and set the All Pages trigger:
Custom Templates in GTM: A Case Study
Save the created tag.


Creating a tag for the Add to Cart event will be a bit more complicated than the Hit tag.
First, you need to pass the product that is added to the cart. As I wrote above, this is possible when the site is configured with advanced e-commerce Google. In that case, the product data is taken from the dataLayer.
To do this we have to create in GTM a variable dataLayer, which will store the ecommerce object for the addToCart event. The variable settings look like this :
Custom Templates in GTM: A Case Study
Second, you need to create a trigger that will activate the tag when the ecommerce addToCart event occurs (the trigger will activate the tag when the dataLayer is pushed into the addToCart event):
Custom Templates in GTM: A Case Study
After creating the variable with the ecommerce object and the trigger, you can start creating the tag :
Custom Templates in GTM: A Case Study
In order :

  1. Select Add To Cart as the tracked event.
  2. Fill in the IDs of the two pixels you want to send data to, separated by commas.
  3. Check "Use multiple price lists" box: in our example we need to use different price lists for Moscow and St. Petersburg.
  4. Fill in the table with price lists.
  5. Check the "Use ecommerce to transfer products and settings" box.
  6. In the ecommerce object of this event, specify the previously created variable.
  7. Set a trigger on the event being tracked, in this case AddToCart.
  8. Save.

Testing of working out

To check if the dynamic retargeting pixels work, you need to activate Preview mode in GTM, go to our site, open the Network section in the browser console and enter ‘rtrg’ in the Filter field:
Custom Templates in GTM: A Case Study
After that we refresh the page and we should have two requests – a Hit event sent in two pixels :
Custom Templates in GTM: A Case Study
Status 200 means that requests are sent and received by the server successfully.
Also in the Preview GTM window, we can see that our created tag is correctly triggered by the Page View event.
To check the event Add To Cart, add the item to cart, and in the console we have two more requests :
Custom Templates in GTM: A Case Study
You can see in the Preview GTM window that the second tag worked successfully. The product data from the dataLayer was loaded and processed correctly, and the correct price list was also inserted.
For the second host the price list is also substituted correctly :
Custom Templates in GTM: A Case Study
Tags for other events are set up and checked in the same way.


Custom templates change the paradigm of how we use GTM. Everyone is used to HTML tags and JS variables, now there is a great alternative.
All you need to do is create a high-quality custom template once, and then anyone familiar with GTM can use it from there.
Given the ability to share the templates you create, I think they should gain popularity among users.
You can download custom template VKontakte dynamic retargeting pixel, which we reviewed in this article.
To import a template, create a new Custom Template and select Import from the menu:
Custom Templates in GTM: A Case Study
Material prepared by me for ppc.world

You may also like