{"id":89271,"date":"2025-02-15T07:43:03","date_gmt":"2025-02-15T07:43:03","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/snowplow-analytics-templates-for-google-tag-manager\/"},"modified":"2025-02-15T07:43:03","modified_gmt":"2025-02-15T07:43:03","slug":"snowplow-analytics-templates-for-google-tag-manager","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=89271","title":{"rendered":"Snowplow Analytics Templates For Google Tag Manager"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>When <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/\">custom templates<\/a> were <a href=\"https:\/\/www.blog.google\/products\/marketingplatform\/analytics\/tag-manager-introducing-custom-templates\/\">released<\/a> for Google Tag Manager, I updated my workflow for working with GTM. Instead of instinctively rushing to the <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-html-tag-guide-google-tag-manager\/\">Custom HTML tag<\/a> and the <a href=\"https:\/\/www.simoahava.com\/analytics\/variable-guide-google-tag-manager\/#4-custom-javascript\">Custom JavaScript variable<\/a>, I started considering whether the custom script that needed to be deployed could be transformed into a custom template first.<\/p>\n<p>While publishing <a href=\"https:\/\/tagmanager.google.com\/gallery\/#\/?filter=gtm-templates-simo-ahava&amp;page=1\">numerous templates<\/a> into the community gallery, I always spent some time over the past 12 months tinkering on an extremely complicated template translation: the <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/javascript-tracker\">Snowplow Analytics JavaScript tracker<\/a>.<\/p>\n<div style=\"aspect-ratio: 2052 \/ 1090;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-blog-logo.jpg\" title=\"Snowplow analytics logo\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1090\" width=\"2052\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-blog-logo.jpg#ZgotmplZ\" alt=\"Snowplow analytics logo\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I\u2019ve written about Snowplow <a href=\"https:\/\/www.simoahava.com\/tags\/snowplow\/\">before<\/a>, and I have a soft spot in my heart for their wonderful DIY approach to the analytics pipeline.<\/p>\n<p>In this article, I\u2019ll introduce the two templates I\u2019ve created, and I\u2019ll share some of the design philosophy with you. It\u2019s been a strange ride, and I\u2019ve found myself pushing the capabilities of custom templates to their limits.<\/p>\n<blockquote>\n<p>This article is <strong>not<\/strong> a manual for the templates. You should turn to the respective <em>technical documentation<\/em> linked in the table below.<\/p>\n<\/blockquote>\n<table>\n<thead>\n<tr>\n<th>Template<\/th>\n<th>Type<\/th>\n<th>Documentation<\/th>\n<th>Gallery<\/th>\n<th>GitHub<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Snowplow Analytics<\/td>\n<td>Tag<\/td>\n<td><a href=\"https:\/\/www.simoahava.com\/custom-templates\/snowplow-analytics\/\">Link<\/a><\/td>\n<td><a href=\"https:\/\/tagmanager.google.com\/gallery\/#\/owners\/snowplow\/templates\/snowplow-gtm-custom-template\">Link<\/a><\/td>\n<td><a href=\"https:\/\/github.com\/snowplow\/snowplow-gtm-custom-template\">Link<\/a><\/td>\n<\/tr>\n<tr>\n<td>Snowplow Analytics Settings<\/td>\n<td>Variable<\/td>\n<td><a href=\"https:\/\/www.simoahava.com\/custom-templates\/snowplow-analytics-settings\/\">Link<\/a><\/td>\n<td><a href=\"https:\/\/tagmanager.google.com\/gallery\/#\/owners\/snowplow\/templates\/snowplow-gtm-custom-template-settings\">Link<\/a><\/td>\n<td><a href=\"https:\/\/github.com\/snowplow\/snowplow-gtm-custom-template-settings\">Link<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"thank-you\">Thank you<\/h2>\n<p>The preliminary, pre-release work on the templates was done by me, but the templates have been released for open-source development under <a href=\"https:\/\/github.com\/snowplow\/snowplow-gtm-custom-template\">Snowplow\u2019s own GitHub organization<\/a>. I believe this to be vital for keeping them up-to-date and linked with the development of the JavaScript tracker.<\/p>\n<p>I want to thank in advance anyone who contributes to the development of these templates, even if it\u2019s just in online discussions, blog comments, or via GitHub Issues.<\/p>\n<p>Huge thanks to <strong>Paul Boocock<\/strong> from Snowplow. He walked through the template with me to spot any inconsistencies, and he also helped establish the setup for open-source development of the templates.<\/p>\n<h2 id=\"the-tag-template\">The tag template<\/h2>\n<p>The <strong>Snowplow Analytics tag template<\/strong> is a fairly faithful implementation of the full Snowplow Analytics JavaScript tracker library.<\/p>\n<div style=\"aspect-ratio: 1540 \/ 994;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-pageview-tag-example.jpg\" title=\"Snowplow Analytics Pageview tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"994\" width=\"1540\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-pageview-tag-example.jpg#ZgotmplZ\" alt=\"Snowplow Analytics Pageview tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The library itself is reminiscent of the <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\">Universal Analytics library<\/a> and the <a href=\"https:\/\/developer.matomo.org\/api-reference\/tracking-javascript\">Matomo (former Piwik) JavaScript SDK<\/a>. These similarities make it easier to translate it to a template, as we can use the native Universal Analytics tag template for inspiration.<\/p>\n<h3 id=\"general-overview\">General overview<\/h3>\n<p>In general, when the tag is fired, it goes through the following motions:<\/p>\n<ol>\n<li>Loads the <strong>tracker library<\/strong> from a self-hosted URL.<\/li>\n<li>Creates the <strong>global namespace<\/strong> for the method name (similar to how Universal Analytics generates the <code>ga<\/code> global object).<\/li>\n<li>Initializes a new (or reuses an existing) <strong>tracker object<\/strong>.<\/li>\n<li>Compiles the settings of the tag into a <strong>command<\/strong> that is then passed to the global method.<\/li>\n<li>The library takes this command from the queue, builds it into a payload, and sends it to the <strong>collector endpoint<\/strong>.<\/li>\n<\/ol>\n<p>All in all, the process is fairly similar to how Universal Analytics works. The biggest difference on a superficial is the DIY nature of the pipeline. You need to <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/Self-hosting-snowplow-js\">self-host the JavaScript library<\/a>, and you need to <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/Setting-up-a-Collector\">build the collector<\/a> yourself.<\/p>\n<h3 id=\"initializing-the-tag\">Initializing the tag<\/h3>\n<p>Converting a library into a template is a complicated task. A lot of time needs to be devoted to understanding how the library works, and then looking at the restrictions of custom templates.<\/p>\n<p>Loading the library and initializing the global namespace was easy &#8211; the <a href=\"https:\/\/developers.google.com\/tag-manager\/templates\/api#injectscript\"><code>injectScript<\/code> API<\/a> handled the first, and a <a href=\"https:\/\/developers.google.com\/tag-manager\/templates\/api#copyfromwindow\"><code>copyFromWindow<\/code><\/a> \/ <a href=\"https:\/\/developers.google.com\/tag-manager\/templates\/api#setinwindow\"><code>setInWindow<\/code><\/a> combo the second.<\/p>\n<p>If the global namespace isn\u2019t established, the script creates it, using a similar queue method that Universal Analytics uses.<\/p>\n<p>Then, the first hiccup is faced: <strong>initializing a tracker<\/strong>.<\/p>\n<p>The Snowplow global method doesn\u2019t have a function to check for the existence of a tracker. There are ways to drill into the object and seek this information, but because there\u2019s no official API for querying tracker status, I didn\u2019t want to start parsing the object without guarantee it will work in the future.<\/p>\n<p>So, to persist information about trackers that have been created, I used a new global array, <code>window._snowplow_trackers<\/code>. This array stores all the trackers that have been initialized, and when the tag fires, the tracker name is checked against this list to verify whether a tracker already exists.<\/p>\n<h4 id=\"major-caveat-with-the-self-hosted-library\">Major caveat with the self-hosted library<\/h4>\n<p>There is a major problem with using <code>injectScript<\/code>.<\/p>\n<p>Because I, as the template author, have no idea about the URLs the template users might load the self-hosted script from, it falls on the template admin to make sure the <strong>template permissions are updated<\/strong> when the self-hosted library URL is changed.<\/p>\n<p>There are instructions for this <a href=\"https:\/\/www.simoahava.com\/custom-templates\/snowplow-analytics\/#advanced-configuration\">here<\/a>.<\/p>\n<blockquote>\n<p><strong>UPDATE 18 May 2020<\/strong>: The template has been updated to support <strong>AWS Cloudfront<\/strong> and <strong>Google Cloud Storage<\/strong> destinations by default (<code>https:\/\/*.cloudfront.net\/*<\/code> and <code>https:\/\/storage.cloud.google.com\/*<\/code>). If you load the library from either destination, you do <strong>not<\/strong> need to update the permissions for <code>injectScript<\/code>!<\/p>\n<\/blockquote>\n<p>Unfortunately, changing permissions <strong>breaks the gallery link<\/strong>, meaning you won\u2019t be notified about updates to the template.<\/p>\n<div style=\"aspect-ratio: 1016 \/ 384;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/broken-link-warning.jpg\" title=\"Broken link warning\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"384\" width=\"1016\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/broken-link-warning.jpg#ZgotmplZ\" alt=\"Broken link warning\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<blockquote>\n<p>I have submitted a feature request to Google about this. Changes to permissions should be uncoupled from the gallery link.<\/p>\n<\/blockquote>\n<h4 id=\"the-tracker-configuration\">The tracker configuration<\/h4>\n<p>The tracker is created with an optional <strong>argument map<\/strong> of configuration settings.<\/p>\n<p>At first, I considered adding all the tracker options as fields into the tag. However, this led to serious bloat, and I wanted to keep the tag as lean as possible (very difficult task, by the way).<\/p>\n<p>So then I thought of just adding a table of parameters where the user can type the key-value pairs they want.<\/p>\n<div style=\"aspect-ratio: 2382 \/ 1176;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/tracker-parameter-table.jpg\" title=\"Tracker parameter table\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1176\" width=\"2382\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/tracker-parameter-table.jpg#ZgotmplZ\" alt=\"Tracker parameter table\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>While this is very functional, it has some drawbacks. It\u2019s very unintuitive, and the user needs to browse the <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/1-General-parameters-for-the-Javascript-tracker\">Snowplow tracker<\/a> documentation to build the argument map correctly.<\/p>\n<h4 id=\"the-snowplow-analytics-settings-variable\">The Snowplow Analytics Settings variable<\/h4>\n<p>Finally, I decided to follow what the <strong>Universal Analytics<\/strong> tag does with the <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-settings-variable-in-gtm\/\">Google Analytics Settings variable<\/a>. The settings variable is <strong>another template<\/strong>, where the user can utilize a nice user interface for setting individual settings.<\/p>\n<div style=\"aspect-ratio: 1792 \/ 1286;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-settings-example.jpg\" title=\"Snowplow Settings example\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1286\" width=\"1792\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-settings-example.jpg#ZgotmplZ\" alt=\"Snowplow Settings example\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once the variable has been created, the user can add it to the Snowplow tag via the drop-down menu.<\/p>\n<p>I decided to leave the parameter table as well. The user can check the <strong>Enable Overriding Settings For This Tag<\/strong> to add\/modify individual parameter fields with the table. Anything added to the table overrides the corresponding parameter in the settings variable.<\/p>\n<p>Though there\u2019s no clear way to use the table to <strong>delete<\/strong> a setting from the variable, setting a parameter name to <code>undefined<\/code> should work.<\/p>\n<h4 id=\"missing-pieces\">Missing pieces<\/h4>\n<p>There\u2019s one thing missing from the tracker configuration process: <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/1-General-parameters-for-the-Javascript-tracker#2219-configuring-cross-domain-tracking\">cross-domain linking<\/a>. As this relies on parsing <strong>HTML elements<\/strong> in the callback, it couldn\u2019t be implemented in the custom template due to restrictions of the sandbox API.<\/p>\n<p>You can still introduce cross-domain linking with a <strong>Custom HTML tag<\/strong>. I also submitted a feature request to Snowplow where cross-domain linking could be done by passing CSS selector strings or <code>href<\/code> patterns via the callback, rather than link elements themselves.<\/p>\n<h3 id=\"building-the-commands\">Building the command(s)<\/h3>\n<p>Once the library is loaded and the tracker is initialized (if necessary), it\u2019s time to take the rest of the settings in the tag and bundle them into a command.<\/p>\n<p>Building the command is split into three different types of user interfaces:<\/p>\n<ol>\n<li>Commands with a generic parameter object.<\/li>\n<li>Commands with special conditions.<\/li>\n<li>Custom commands.<\/li>\n<\/ol>\n<h4 id=\"parameter-object\">Parameter object<\/h4>\n<p>The Snowplow JavaScript tracker uses <strong>positional arguments<\/strong> to handle command logic. This means that the arguments need to be provided to the global method in a specific order, so that the values correspond with their correct, functional counterparts.<\/p>\n<p>For example, <strong>ad tracking<\/strong> and impression requires the command to be compiled like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">snowplow_name_here(<span style=\"color:#a50\">'trackAdImpression'<\/span>,\n    <span style=\"color:#a50\">'67965967893'<\/span>,             <span style=\"color:#aaa;font-style:italic\">\/\/ impressionId\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'cpm'<\/span>,                     <span style=\"color:#aaa;font-style:italic\">\/\/ costModel - 'cpa', 'cpc', or 'cpm'\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>     <span style=\"color:#099\">5.5<\/span>,                      <span style=\"color:#aaa;font-style:italic\">\/\/ cost\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'http:\/\/www.example.com'<\/span>,  <span style=\"color:#aaa;font-style:italic\">\/\/ targetUrl\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'23'<\/span>,                      <span style=\"color:#aaa;font-style:italic\">\/\/ bannerId\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'7'<\/span>,                       <span style=\"color:#aaa;font-style:italic\">\/\/ zoneId\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'201'<\/span>,                     <span style=\"color:#aaa;font-style:italic\">\/\/ advertiserId\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#a50\">'12'<\/span>                       <span style=\"color:#aaa;font-style:italic\">\/\/ campaignId\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>);\n<\/code><\/pre>\n<\/div>\n<p>Having the architecture setup like this makes life difficult for this particular template author. Because of this, any named arguments need to be mapped into positional arguments. With parameter-based objects, it means that the user can create a parameter map either using a Google Tag Manager variable or the parameter table, and the object would look like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">{\n  impressionId: <span style=\"color:#a50\">'67965967893'<\/span>,\n  costModel: <span style=\"color:#a50\">'cpm'<\/span>,\n  cost: <span style=\"color:#099\">5.5<\/span>\n}\n<\/code><\/pre>\n<\/div>\n<p>But because Snowplow uses positional arguments, I can\u2019t simply attach this object to the call. That would have been beautifully elegant and simple, <strong>and would have shortened the template codebase by about a half<\/strong>!<\/p>\n<p>Instead, the code needs to map these named parameters to their correct positional counterparts:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Pseudo-code\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>snowplow_name_here(<span style=\"color:#a50\">'trackAdImpression'<\/span>,\n  data.argMap.impressionId,\n  data.argMap.costModel,\n  data.argMap.cost\n);\n<\/code><\/pre>\n<\/div>\n<p>I <em>could<\/em> have taken the easy way out and required the user to pass the positions themselves by returning an array instead of an object, but that would have made for a very clunky experience.<\/p>\n<p>With named arguments, I can also do validation:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">if<\/span> ([<span style=\"color:#a50\">'cpa'<\/span>, <span style=\"color:#a50\">'cpc'<\/span>, <span style=\"color:#a50\">'cpm'<\/span>].indexOf(data.argMap.costModel) === -<span style=\"color:#099\">1<\/span>) <span style=\"color:#00a\">return<\/span> fail(<span style=\"color:#a50\">'Invalid \"costModel\" argument provided in trackAdImpression call!'<\/span>);\n<\/code><\/pre>\n<\/div>\n<p>Unfortunately, as mentioned above, mapping the named parameters to their positional counterparts leads to a lot of extra code. I have submitted a feature request to Snowplow to support object arguments as well, as that would elegantly solve the whole problem (and converge with how Universal Analytics works, for example).<\/p>\n<h4 id=\"special-commands\">Special commands<\/h4>\n<p>For commands with extra functionality, such as <strong>Page View<\/strong> and <strong>Link Tracking<\/strong>, I needed to create their own field groups. I <em>could<\/em> have gone forward with just an argument map again, but with e.g. <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/2-Specific-event-tracking-with-the-Javascript-tracker#321-enableactivitytracking\">page ping tracking<\/a> and <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/2-Specific-event-tracking-with-the-Javascript-tracker#enableLinkClickTracking\">automatic link tracking<\/a> the argument maps wouldn\u2019t suffice, as they needed to be executed as separate, additional commands.<\/p>\n<p>Building these special groups wasn\u2019t problematic per se, but there were cases where I was pushing against the limitations of the custom template sandbox. For example, <em>automatic link tracking<\/em> lets you add element-based filters and contexts to the call. Because the sandbox doesn\u2019t let you interact with HTML elements, using these features is impossible.<\/p>\n<p>That\u2019s why automatic link tracking and <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/2-Specific-event-tracking-with-the-Javascript-tracker#3101-enableformtracking\">form tracking<\/a> have been stripped to just the <strong>whitelist<\/strong> and <strong>blacklist<\/strong> features.<\/p>\n<h4 id=\"custom-commands\">Custom commands<\/h4>\n<p>Finally, there are many commands in the Snowplow library that aren\u2019t directly related to an event. These are all grouped under <strong>Custom Commands<\/strong>.<\/p>\n<p>Many of these are just plain commands without any arguments. In such instances, the <strong>Command argument<\/strong> field is not shown.<\/p>\n<div style=\"aspect-ratio: 1066 \/ 574;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/flushbuffer-custom-command.jpg\" title=\"flushBuffer custom command\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"574\" width=\"1066\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/flushbuffer-custom-command.jpg#ZgotmplZ\" alt=\"flushBuffer custom command\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In other cases, there\u2019s a field where the user can add the argument(s).<\/p>\n<div style=\"aspect-ratio: 1398 \/ 630;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/custom-command-global-contexts.jpg\" title=\"Global Contexts command\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"630\" width=\"1398\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/custom-command-global-contexts.jpg#ZgotmplZ\" alt=\"Global Contexts command\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There\u2019s only very little validation for these arguments. Typically it\u2019s making sure the parameter is in the correct format, and with e.g. <code>enableGdprContext<\/code> it turns a comma-separated string into an array.<\/p>\n<p>However, the expectation is that when the template user adds custom commands, they either know exactly what they\u2019re doing, or they\u2019re reading documentation that explains how to set these things up.<\/p>\n<p>In general, I\u2019d recommend using Google Tag Manager variables instead of hard-coding the arguments. It makes it easier to switch on the fly if necessary.<\/p>\n<h3 id=\"firing-the-commands\">Firing the commands<\/h3>\n<p>Finally, once the tracker is created and the commands have been built, they are all executed in order by invoking the global method.<\/p>\n<p>For example, creating a new tracker, setting up page ping tracking, and sending a pageview would end up with the following items added to the global queue for execution (in order):<\/p>\n<div style=\"aspect-ratio: 1336 \/ 738;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-queue.jpg\" title=\"Snowplow Queue\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"738\" width=\"1336\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/snowplow-queue.jpg#ZgotmplZ\" alt=\"Snowplow Queue\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"summary\">Summary<\/h2>\n<p>Building the template has been a very educational journey, and it also helped spawn a number of feature requests for Google Tag Manager and for Snowplow\u2019s JavaScript tracker, each of which I\u2019m sure will make both solutions even stronger.<\/p>\n<p>Early on I had an idea of creating a super smooth user interface, where every single parameter, event, and option was confined to its own field with full validation, value hints, help texts, and so forth.<\/p>\n<p>This was a bad idea.<\/p>\n<p>First of all, there\u2019s a limitation of something like <strong>100 fields<\/strong> in any given template. Before you shout in indignation, <strong>it makes perfect sense<\/strong>. Having a behemoth of a template forced upon the UI every single time a new tag or variable is created is not favorable to Google Tag Manager\u2019s performance.<\/p>\n<p>Also, if you have a template that requires 100 fields, it\u2019s possible you should look into modularizing it.<\/p>\n<p>The Snowplow template is <strong>big<\/strong>. It\u2019s close to the 100 field limit. I know this because I ran into the limit a couple of times and had to refactor. If it turns out to be a performance hog, I\u2019ll look into splitting it up into smaller parts (e.g. one template for most used events, another for the rest).<\/p>\n<p>Separating the tracker configuration into its own variable template was a step towards modularity, and depending on the feedback we\u2019ll (Snowplow and I) see if there needs to be more of it.<\/p>\n<p>I hope this article has been useful. I\u2019m still a huge fan of custom templates and firmly believe they are the future of Google\u2019s tag management. I do hope that new APIs are consistently added to the sandbox, as some of the limitations are pretty jarring especially when methods you\u2019d expect most JavaScript libraries to leverage are blocked (I\u2019m looking at you, <code>JSON.parse<\/code>).<\/p>\n<p>If you have feedback about the templates, be sure to raise them as <strong>issues<\/strong> in the respective repositories: <a href=\"https:\/\/github.com\/snowplow\/snowplow-gtm-custom-template\/issues\">Snowplow Analytics tag template<\/a> and <a href=\"https:\/\/github.com\/snowplow\/snowplow-gtm-custom-template-settings\/issues\">Snowplow Analytics Settings variable template<\/a>.<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>When custom templates were released for Google Tag Manager, I updated my workflow for working with GTM. Instead of instinctively rushing to the Custom HTML tag and the Custom JavaScript variable, I started considering whether the custom script that needed to be deployed could be transformed into a custom template first. While publishing numerous templates [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":89272,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12765,1531,14495,43260,1791,11455],"dealstore":[],"offerexpiration":[],"class_list":["post-89271","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-analytics","tag-google","tag-manager","tag-snowplow","tag-tag","tag-templates"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Snowplow Analytics Templates For Google Tag Manager - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=89271\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Snowplow Analytics Templates For Google Tag Manager - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"When custom templates were released for Google Tag Manager, I updated my workflow for working with GTM. Instead of instinctively rushing to the Custom HTML tag and the Custom JavaScript variable, I started considering whether the custom script that needed to be deployed could be transformed into a custom template first. While publishing numerous templates [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=89271\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-15T07:43:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2052\" \/>\n\t<meta property=\"og:image:height\" content=\"1090\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=89271#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Snowplow Analytics Templates For Google Tag Manager\",\"datePublished\":\"2025-02-15T07:43:03+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271\"},\"wordCount\":2145,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg\",\"keywords\":[\"Analytics\",\"Google\",\"Manager\",\"Snowplow\",\"TAG\",\"Templates\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=89271#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=89271\",\"url\":\"https:\/\/fivemor.com\/?p=89271\",\"name\":\"Snowplow Analytics Templates For Google Tag Manager - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg\",\"datePublished\":\"2025-02-15T07:43:03+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=89271#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=89271\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=89271#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg\",\"width\":2052,\"height\":1090},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=89271#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Snowplow Analytics Templates For Google Tag Manager\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Snowplow Analytics Templates For Google Tag Manager - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=89271","og_locale":"en_US","og_type":"article","og_title":"Snowplow Analytics Templates For Google Tag Manager - Som2ny Network","og_description":"When custom templates were released for Google Tag Manager, I updated my workflow for working with GTM. Instead of instinctively rushing to the Custom HTML tag and the Custom JavaScript variable, I started considering whether the custom script that needed to be deployed could be transformed into a custom template first. While publishing numerous templates [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=89271","og_site_name":"Som2ny Network","article_published_time":"2025-02-15T07:43:03+00:00","og_image":[{"width":2052,"height":1090,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=89271#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=89271"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Snowplow Analytics Templates For Google Tag Manager","datePublished":"2025-02-15T07:43:03+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=89271"},"wordCount":2145,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=89271#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg","keywords":["Analytics","Google","Manager","Snowplow","TAG","Templates"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=89271#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=89271","url":"https:\/\/fivemor.com\/?p=89271","name":"Snowplow Analytics Templates For Google Tag Manager - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=89271#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=89271#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg","datePublished":"2025-02-15T07:43:03+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=89271#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=89271"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=89271#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-blog-logo.jpg","width":2052,"height":1090},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=89271#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Snowplow Analytics Templates For Google Tag Manager"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/89271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=89271"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/89271\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/89272"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=89271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=89271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=89271"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=89271"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=89271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}