{"id":98074,"date":"2025-02-19T20:38:29","date_gmt":"2025-02-19T20:38:29","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/how-to-build-a-google-tag-manager-monitor\/"},"modified":"2025-02-19T20:38:29","modified_gmt":"2025-02-19T20:38:29","slug":"how-to-build-a-google-tag-manager-monitor","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=98074","title":{"rendered":"How To Build A Google Tag Manager Monitor"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p><a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a> is strictly a <em>tag delivery system<\/em>, and it\u2019s very careful <a href=\"https:\/\/support.google.com\/tagmanager\/answer\/9323295#data\">not to collect any analytics data<\/a> on its own. This is most likely a deliberate choice, because if GTM was to start collecting data, it would introduce additional barriers to adoption.<\/p>\n<p>Nevertheless, being a tool that consolidates the design, development, deployment, and testing of all the marketing and analytics pixels, code snippets, and utilities running on a website or a mobile app, lacking the necessary features for auditing and monitoring has always seemed like an oversight.<\/p>\n<div style=\"aspect-ratio: 1152 \/ 395;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/google-tag-manager-monitor.jpg\" title=\"Google Tag Mangager Monitor\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"395\" width=\"1152\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/google-tag-manager-monitor.jpg#ZgotmplZ\" alt=\"Google Tag Mangager Monitor\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Recently, Google Tag Manager introduced a new API for <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/\">custom templates<\/a>: <a href=\"https:\/\/developers.google.com\/tag-manager\/templates\/api#addEventCallback\"><code>addEventCallback<\/code><\/a>. This API lets you create your own monitoring system for your container. With community effort and support from Google, the new API will introduce a number of solutions around tag monitoring and auditing in the near future.<\/p>\n<p>In this article, <strong>Mark Edmondson<\/strong> and I will help you get started. We\u2019ll introduce how the new API works and how you can use it to build the necessary tags and triggers in GTM for monitoring purposes. We\u2019ll also show you how to use powerful, cheap, and scalable features of the <a href=\"https:\/\/cloud.google.com\/\">Google Cloud Platform<\/a> to create the backend for the monitoring system, and finally we\u2019ll give you some ideas for how to visualize the data in a <a href=\"https:\/\/datastudio.google.com\/\">Google Data Studio<\/a> report.<\/p>\n<p>You can also check out <a href=\"https:\/\/youtu.be\/OGvsFTEXFGc\">this video<\/a>, where I go through (almost) the same steps in video format.<\/p>\n<p>\n  <iframe src=\"https:\/\/www.youtube-nocookie.com\/embed\/OGvsFTEXFGc?enablejsapi=1\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" allowfullscreen=\"\" title=\"YouTube Video\"><\/iframe>\n<\/p>\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=\"guest-author-mark-edmondson\">Guest author: Mark Edmondson<\/h2>\n<p><a href=\"https:\/\/twitter.com\/holomarked\">Mark Edmondson<\/a> is a former colleague of mine, a fellow <a href=\"https:\/\/google-developers.appspot.com\/community\/experts\/directory\/profile\/profile-mark_edmondson\">Google Developer Expert<\/a> for Google Analytics, and the owner of one of the most versatile skill sets in our industry. He\u2019s currently working as a data engineer at <a href=\"https:\/\/iihnordic.com\/\">IIH Nordic<\/a> (a great company full of nice folks in its own right!).<\/p>\n<p>Mark is fluent in bridging together the worlds of marketing and analytics, and his reach extends far beyond, as evidenced by his <a href=\"https:\/\/github.com\/MarkEdmondson1234\/\">prolific contributions to the open-source space<\/a>. He\u2019s the author of the popular <a href=\"https:\/\/github.com\/MarkEdmondson1234\/googleAnalyticsR\">googleAnalyticsR<\/a> library for R, and <a href=\"https:\/\/www.markedmondson.me\/\">his blog<\/a> is full of great walkthroughs for data engineering, data science, web analytics, and software development.<\/p>\n<p>Being one of the most humble people I\u2019ve met, he\u2019s probably royally embarrassed by this introduction. So I\u2019ll just wrap up this introduction by making it clear how grateful I am for his contributions not just to this article but to our whole industry.<\/p>\n<h2 id=\"what-well-build-the-google-tag-manager-monitor\">What we\u2019ll build: The Google Tag Manager Monitor<\/h2>\n<p>The <strong>key deliverable<\/strong> of this little project is a <a href=\"https:\/\/cloud.google.com\/bigquery\/\">BigQuery<\/a> view that collects data from your website. This data will be sent by way of a new Google Tag Manager custom template, and it will comprise statistics of all the tags that have fired on your site for any given <code>dataLayer<\/code> event.<\/p>\n<div style=\"aspect-ratio: 915 \/ 464;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-view.jpg\" title=\"BigQuery view\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"464\" width=\"915\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-view.jpg#ZgotmplZ\" alt=\"BigQuery view\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In this proof-of-concept, the metadata collected from each <code>dataLayer<\/code> event includes:<\/p>\n<ul>\n<li>\n<p>Event name and timestamp (to uniquely distinguish events with the same name from each other).<\/p>\n<\/li>\n<li>\n<p>Tag ID, name, firing status, and execution time for every tag that fired for that event (or <code>null<\/code> if no tags fired for the event).<\/p>\n<\/li>\n<\/ul>\n<p>It\u2019s extremely easy to extend this solution to include things like Container ID or tag categorization information &#8211; we\u2019ll explore these later in the guide.<\/p>\n<h2 id=\"why-do-we-need-a-monitor\">Why do we need a monitor?<\/h2>\n<p>There aren\u2019t that many ways to monitor the consistency of tags firing in your containers. Obviously, you can run reports against the various endpoints, such as Google Analytics or Facebook, and detect anomalous data collection through their reporting interfaces.<\/p>\n<p>But this has some problems:<\/p>\n<ul>\n<li>\n<p>Not all endpoints have reports readily available.<\/p>\n<\/li>\n<li>\n<p>With endpoints collecting data from multiple different sources, it\u2019s difficult to identify those that come from GTM and those that do not.<\/p>\n<\/li>\n<li>\n<p>It\u2019s difficult to know if the data issues in the endpoints are due to data collection or something else (e.g. a broken filter in Google Analytics).<\/p>\n<\/li>\n<\/ul>\n<p>In other words, if you want to be alerted to issues in data collection, the best way to do this is to monitor the data collection itself.<\/p>\n<p>For this to be possible, the data collection mechanism requires the possibility to introduce a <strong>side effect<\/strong>. The side effect in this case is the <code>addEventCallback<\/code> API, and its purpose is to collect metadata about the data collection preferably to an endpoint that is <strong>separated from the tags that are being monitored<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1303 \/ 728;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/process-flow.jpg\" title=\"Process flow\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"728\" width=\"1303\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/process-flow.jpg#ZgotmplZ\" alt=\"Process flow\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Why have a separate endpoint for monitoring? Well, it wouldn\u2019t really make sense to collect data about Google Analytics\u2019 data collection in Google Analytics itself (a custom reporting property, for example), because if the analytics data collection fails due to not being able to connect to the GA endpoint, then the monitoring data collection would fail as well.<\/p>\n<p>That is why this article utilizes an endpoint in the Google Cloud Platform to collect the data. It\u2019s not failsafe, either, since if the data collection fails due to a network outage, the collection to GCP would fail, too. Even though there are ways to tackle this problem as well, we\u2019ll try to keep things at an MVP (minimum viable product) level, and you can then extend the solution to fit your more elaborate needs.<\/p>\n<h2 id=\"step-1-build-the-custom-template\">Step 1: Build the custom template<\/h2>\n<p>The custom <strong>tag<\/strong> template will create a <strong>callback<\/strong> that is called every time a <code>dataLayer<\/code> event reaches conclusion. This is basically the same thing as the <a href=\"https:\/\/www.simoahava.com\/gtm-tips\/hitcallback-eventcallback\/\">eventCallback<\/a> of Google Tag Manager, just wrapped in a template API.<\/p>\n<div style=\"aspect-ratio: 611 \/ 209;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/data-object.jpg\" title=\"The data object\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"209\" width=\"611\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/data-object.jpg#ZgotmplZ\" alt=\"The data object\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When the callback is invoked, an array of tags is passed to the callback in a data object. Each tag is reported with the following metadata:<\/p>\n<ul>\n<li>\n<p><code>id<\/code>: Tag ID<\/p>\n<\/li>\n<li>\n<p><code>status<\/code>: Firing status (e.g. success, failure)<\/p>\n<\/li>\n<li>\n<p><code>executionTime<\/code>: Execution time in milliseconds<\/p>\n<\/li>\n<\/ul>\n<p>We\u2019ll augment this data set with these values:<\/p>\n<p>It\u2019s easiest to show by doing.<\/p>\n<p>In Google Tag Manager, open up the <strong>Templates<\/strong> view, and create a new tag template.<\/p>\n<p>You can also download the template from <a href=\"https:\/\/github.com\/sahava\/GoogleTagManagerTemplates\/tree\/master\/tags\/Utilities\/GoogleTagManagerMonitor\">here<\/a> and <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#importing-and-exporting\">import<\/a> it directly into your container.<\/p>\n<h3 id=\"11-fields\">1.1. Fields<\/h3>\n<p>Feel free to add whatever you want into the <strong>Info<\/strong> tab of template. We went with this:<\/p>\n<div style=\"aspect-ratio: 455 \/ 402;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/info-tab.jpg\" title=\"Info tab\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"402\" width=\"455\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/info-tab.jpg#ZgotmplZ\" alt=\"Info tab\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In the Fields tab, we\u2019re going with two default fields. One for the <strong>endpoint URL<\/strong>, and one to let the user decide whether to <strong>batch<\/strong> the hits or not.<\/p>\n<p>There is a third field as well, which becomes available if the user decided to batch the hits.<\/p>\n<h4 id=\"111-endpoint-field\">1.1.1. <code>endPoint<\/code> field<\/h4>\n<p>The first field is a <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#text-input\">Text input<\/a> field named <code>endPoint<\/code>.<\/p>\n<div style=\"aspect-ratio: 943 \/ 710;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/endpoint-field.jpg\" title=\"Endpoint field\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"710\" width=\"943\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/endpoint-field.jpg#ZgotmplZ\" alt=\"Endpoint field\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It has the following <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#field-configuration\">Field configurations<\/a> toggled on:<\/p>\n<ul>\n<li>\n<p><strong>Value hint<\/strong>: <code>e.g. https:\/\/track.com\/collect<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Display name<\/strong>: <code>GET request endpoint<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Help text<\/strong>: <code>Provide the URL to which the GET request with tag data is sent.<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Always in summary<\/strong>: checked<\/p>\n<\/li>\n<li>\n<p><strong>Validation rules<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>There are two <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#validation-rules\">validation rules<\/a>:<\/p>\n<p>If you want, you can add some customized error messages to these validation rules by opening the Advanced settings for each rule.<\/p>\n<div style=\"aspect-ratio: 891 \/ 336;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/validation-rules-advanced-settings.jpg\" title=\"Validation rules advanced settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"336\" width=\"891\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/validation-rules-advanced-settings.jpg#ZgotmplZ\" alt=\"Validation rules advanced settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h4 id=\"112-batchhits-field\">1.1.2. <code>batchHits<\/code> field<\/h4>\n<p>The second field is a <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#radio-buttons\">Radio button<\/a> field named <code>batchHits<\/code>.<\/p>\n<div style=\"aspect-ratio: 952 \/ 668;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/batchhits.jpg\" title=\"Batch hits field\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"668\" width=\"952\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/batchhits.jpg#ZgotmplZ\" alt=\"Batch hits field\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It has the following Field configurations toggled on:<\/p>\n<ul>\n<li>\n<p><strong>Display name<\/strong>: <code>Batch hits<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Help text<\/strong>: <code>If you select No, details of all the tags that fired for any given hit are sent in a single GET request. If you select Yes, you can choose the maximum number of tags per request, and the tag will automatically send multiple requests if necessary.<\/code><\/p>\n<\/li>\n<\/ul>\n<p>There are two radio buttons.<\/p>\n<p>The <strong>first<\/strong> one is named <code>No<\/code>, and has the value <code>no<\/code>.<\/p>\n<p>The <strong>second<\/strong> one is named <code>Yes<\/code>, and has the value <code>yes<\/code>.<\/p>\n<p>The Yes button has a nested field (found after showing advanced settings for the button), which is a <strong>Text input<\/strong> field named <code>maxTags<\/code>.<\/p>\n<div style=\"aspect-ratio: 873 \/ 356;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/radio-yes-nested-field.jpg\" title=\"Radio button nested field\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"356\" width=\"873\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/radio-yes-nested-field.jpg#ZgotmplZ\" alt=\"Radio button nested field\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h4 id=\"113-maxtags-field\">1.1.3. <code>maxTags<\/code> field<\/h4>\n<p>The <em>nested<\/em> <code>maxTags<\/code> field is a <strong>Text input<\/strong> field where the user can set a limit of tags sent per request.<\/p>\n<p>This is useful if you have tags with long names or <code>dataLayer<\/code> events that can fire dozens of tags at a time. Sending the requests in batches should reduce the effort required in the endpoint to parse the data into the BigQuery table.<\/p>\n<div style=\"aspect-ratio: 858 \/ 562;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/maxtags-field.jpg\" title=\"maxTags field\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"562\" width=\"858\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/maxtags-field.jpg#ZgotmplZ\" alt=\"maxTags field\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The field has the following Field configurations toggled on:<\/p>\n<ul>\n<li>\n<p><strong>Display name<\/strong>: <code>Maximum number of tags per request<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Help text<\/strong>: <code>Enter the maximum number of tags per request that will be dispatched to the endpoint. If necessary, multiple requests will be made.<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Default value<\/strong>: <code>10<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Validation rules<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>The sole validation rule is <strong>This value must be a positive integer<\/strong>.<\/p>\n<h3 id=\"12-code\">1.2. Code<\/h3>\n<p>In the <strong>Code<\/strong> tab, add the following code:<\/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\">\/\/ Require the necessary APIs\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> addEventCallback = require(<span style=\"color:#a50\">'addEventCallback'<\/span>);\n<span style=\"color:#00a\">const<\/span> readFromDataLayer = require(<span style=\"color:#a50\">'copyFromDataLayer'<\/span>);\n<span style=\"color:#00a\">const<\/span> sendPixel = require(<span style=\"color:#a50\">'sendPixel'<\/span>);\n<span style=\"color:#00a\">const<\/span> getTimestamp = require(<span style=\"color:#a50\">'getTimestamp'<\/span>);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Get the dataLayer event that triggered the tag\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> event = readFromDataLayer(<span style=\"color:#a50\">'event'<\/span>);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Add a timestamp to separate events named the same way from each other\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> eventTimestamp = getTimestamp();\n\n<span style=\"color:#00a\">const<\/span> endPoint = data.endPoint;\n<span style=\"color:#00a\">const<\/span> batchHits = data.batchHits === <span style=\"color:#a50\">'yes'<\/span>;\n<span style=\"color:#00a\">const<\/span> maxTags = data.maxTags;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Utility for splitting an array into multiple arrays of given size\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> splitToBatches = (arr, size) =&gt; {\n  <span style=\"color:#00a\">const<\/span> newArr = [];\n  <span style=\"color:#00a\">for<\/span> (<span style=\"color:#00a\">let<\/span> i = <span style=\"color:#099\">0<\/span>, len = arr.length; i return newArr;\n};\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ The addEventCallback gets two arguments: container ID and a data object with an array of tags that fired\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>addEventCallback((ctid, eventData) =&gt; {\n\n  <span style=\"color:#aaa;font-style:italic\">\/\/ Filter out tags that have the \"exclude\" metadata set to true\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">const<\/span> tags = eventData.tags.filter(t =&gt; t.exclude !== <span style=\"color:#a50\">'true'<\/span>);\n  \n  <span style=\"color:#aaa;font-style:italic\">\/\/ If batching is enabled, split the tags into batches of the given size\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">const<\/span> batches = batchHits ? splitToBatches(tags, maxTags) : ;\n  \n  <span style=\"color:#aaa;font-style:italic\">\/\/ For each batch, build a payload and dispatch to the endpoint as a GET request\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  batches.forEach(tags =&gt; {\n    <span style=\"color:#00a\">let<\/span> payload = <span style=\"color:#a50\">'?eventName='<\/span> + event + <span style=\"color:#a50\">'&amp;eventTimestamp='<\/span> + eventTimestamp;\n    tags.forEach((tag, idx) =&gt; {\n      <span style=\"color:#00a\">const<\/span> tagPrefix = <span style=\"color:#a50\">'&amp;tag'<\/span> + (idx + <span style=\"color:#099\">1<\/span>);\n      payload +=\n        tagPrefix + <span style=\"color:#a50\">'id='<\/span> + tag.id +\n        tagPrefix + <span style=\"color:#a50\">'nm='<\/span> + tag.name +\n        tagPrefix + <span style=\"color:#a50\">'st='<\/span> + tag.status +\n        tagPrefix + <span style=\"color:#a50\">'et='<\/span> + tag.executionTime;\n    });\n    sendPixel(endPoint + payload, <span style=\"color:#00a\">null<\/span>, <span style=\"color:#00a\">null<\/span>);\n  });\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ After adding the callback, signal tag completion\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>data.gtmOnSuccess();\n<\/code><\/pre>\n<\/div>\n<p>The functionally most significant part of this code is the <code>addEventCallback()<\/code> method. This invokes the API of the same name.<\/p>\n<p>The API checks the <code>dataLayer<\/code> event that triggered the tag created from this template, and then updates the <code>dataLayer<\/code> <code>eventCallback<\/code> with the function defined in the template.<\/p>\n<p>In other words, when whatever event that triggers this monitor tag is fully resolved (i.e. all tags have signalled completion), the callback function will execute with data about each of the tags that fired on the event.<\/p>\n<p>There\u2019s a provision in place to exclude any tags from the monitoring based on a tag metadata field you can set (more on this below). For some, it might make sense to exclude the monitoring tag itself from being monitored, even though there might be value in measuring its execution time along with all the other tags\u2019.<\/p>\n<p>By far the most significant code is run when the payload is built:<\/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\">batches.forEach(tags =&gt; {\n   <span style=\"color:#00a\">let<\/span> payload = <span style=\"color:#a50\">'?eventName='<\/span> + event + <span style=\"color:#a50\">'&amp;eventTimestamp='<\/span> + eventTimestamp;\n   tags.forEach((tag, idx) =&gt; {\n      <span style=\"color:#00a\">const<\/span> tagPrefix = <span style=\"color:#a50\">'&amp;tag'<\/span> + (idx + <span style=\"color:#099\">1<\/span>);\n      payload +=\n       tagPrefix + <span style=\"color:#a50\">'id='<\/span> + tag.id +\n       tagPrefix + <span style=\"color:#a50\">'nm='<\/span> + tag.name +\n       tagPrefix + <span style=\"color:#a50\">'st='<\/span> + tag.status +\n       tagPrefix + <span style=\"color:#a50\">'et='<\/span> + tag.executionTime;\n   });\n   sendPixel(endPoint + payload, <span style=\"color:#00a\">null<\/span>, <span style=\"color:#00a\">null<\/span>);\n});\n<\/code><\/pre>\n<\/div>\n<p>Here, every tag that fired on the event is parsed and concatenated into a URL query string payload. For example, a <code>dataLayer<\/code> event named <code>gtm.js<\/code> that fired two tags might have the following payload:<\/p>\n<pre>\n?eventName=gtm.js\n&amp;eventTimestamp=1562402273899\n&amp;tag1id=12\n&amp;tag1nm=GA-PageView\n&amp;tag1st=success\n&amp;tag1et=124\n&amp;tag2id=29\n&amp;tag2nm=Facebook\n&amp;tag2st=failure\n&amp;tag2et=422\n<\/pre>\n<p>This is then joined with the endpoint URL, and a GET request is dispatched.<\/p>\n<p>This is the payload that is captured by the endpoint in the Google Cloud, which Mark will show <a href=\"#step-3-creating-the-google-cloud-platform-endpoint\">later<\/a> how to create.<\/p>\n<p>It\u2019s easy to extend this with additional metadata. Just update the <code>payload +=<\/code> concatenation with each additional metadata key you want to access from the tags.<\/p>\n<h3 id=\"13-permissions\">1.3. Permissions<\/h3>\n<p>If you\u2019ve added the code into the code editor without errors, you should see three permissions predefined in the <strong>Permissions<\/strong> tab.<\/p>\n<div style=\"aspect-ratio: 591 \/ 466;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/permissions-tab.jpg\" title=\"Permissions tab\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"466\" width=\"591\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/permissions-tab.jpg#ZgotmplZ\" alt=\"Permissions tab\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Naturally, you won\u2019t know the endpoint until you\u2019ve created it, so if you want to test the template before creating the endpoint, you can just a placeholder in the permissions such as <code>https:\/\/placeholder.com\/collect<\/code>.<\/p>\n<p>You can <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#testing-the-template\"><strong>Test<\/strong><\/a> the template if you wish, but it won\u2019t really do anything, as the callback requires an event to be pushed into <code>dataLayer<\/code> to trigger.<\/p>\n<p><strong>Save<\/strong> the template once you\u2019re done.<\/p>\n<p>The first thing we\u2019ll need to do is create the <strong>monitor tag<\/strong> itself.<\/p>\n<h3 id=\"21-create-the-monitor-tag\">2.1. Create the monitor tag<\/h3>\n<p>In GTM, go to <strong>Tags<\/strong> and create a new tag. Select the template you just created from the tag type selector.<\/p>\n<div style=\"aspect-ratio: 513 \/ 273;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/choose-tag-type.jpg\" title=\"Choose tag type\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"273\" width=\"513\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/choose-tag-type.jpg#ZgotmplZ\" alt=\"Choose tag type\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The GET request endpoint field takes the endpoint you\u2019ll create in the next section, so you can just use the placeholder value of <code>https:\/\/placeholder.com\/collect<\/code> there for now, we\u2019ll return to this shortly.<\/p>\n<p>If you want to send the hits in batches, choose the <strong>Yes<\/strong> radio button, but you might want to start without batches and just use the default settings.<\/p>\n<p>Next, expand <strong>Advanced Settings<\/strong> and scroll down to a new setting named <strong>Additional Tag Metadata<\/strong>.<\/p>\n<p>Click it open, and check the box named <strong>Include tag name<\/strong>. Set the <strong>Key for tag name<\/strong> value to <code>name<\/code>.<\/p>\n<p>Add a new metadata by clicking the <strong>+ Add Metadata<\/strong> button, and set these values:<\/p>\n<div style=\"aspect-ratio: 876 \/ 650;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/additional-tag-metadata.jpg\" title=\"Additional tag metadata\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"650\" width=\"876\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/additional-tag-metadata.jpg#ZgotmplZ\" alt=\"Additional tag metadata\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Because this monitor should monitor every single event that fires in your container, add a new <strong>Custom Event Trigger<\/strong> to the tag that looks like this:<\/p>\n<div style=\"aspect-ratio: 1175 \/ 542;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/all-events.jpg\" title=\"Custom event trigger for all events\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"542\" width=\"1175\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/all-events.jpg#ZgotmplZ\" alt=\"Custom event trigger for all events\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>With that regular expression for the event name, the monitor tag will fire for every single <code>dataLayer<\/code> event.<\/p>\n<p>Save the trigger and tag when ready.<\/p>\n<h3 id=\"22-the-additional-tag-metadata-setting\">2.2. The Additional Tag Metadata setting<\/h3>\n<p>As you can see, there\u2019s a <strong>new advanced setting<\/strong> for Google Tag Manager tags.<\/p>\n<p>This <strong>Additional Tag Metadata<\/strong> setting lets you add metadata to every single tag in the container. This metadata is added to the tags passed to the <code>eventCallback<\/code> of the <code>dataLayer.push()<\/code> (such as the one added by the <code>addEventCallback<\/code> custom template API).<\/p>\n<p>One predefined metadata is already given to you: the <strong>tag name<\/strong>. By checking the <strong>Include tag name<\/strong> box, the <code>eventCallback<\/code> data object will always include the <code>name<\/code> key (or whatever key you assign), automatically set to the tag\u2019s name.<\/p>\n<div style=\"aspect-ratio: 838 \/ 350;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/other-metadata.jpg\" title=\"Other metadata\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"350\" width=\"838\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/other-metadata.jpg#ZgotmplZ\" alt=\"Other metadata\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can add additional metadata too, such as category or type of tag.<\/p>\n<p>Just remember that the solution as established in this article only makes use of the tag <code>name<\/code> and <code>exclude<\/code> metadata keys. Any other metadata you add will require a code change in the template, as the new metadata will need to be concatenated into the payload string as well.<\/p>\n<h3 id=\"23-update-all-your-tags\">2.3. Update all your tags<\/h3>\n<p>The last thing you\u2019ll need to do in GTM (apart from updating the endpoint URL of the monitor tag when you have one), is to update <strong>every single one<\/strong> of your tags to include the tag <code>name<\/code> metadata. The tag name is easier to interpret than the tag ID when building your reports and visualizations.<\/p>\n<p>For each tag, expand the <strong>Advanced Settings<\/strong> and check the <strong>Include tag name<\/strong> checkbox under <strong>Additional Tag Metadata<\/strong>. Remember to set the key name to <code>name<\/code> as well.<\/p>\n<p>Hopefully, the tag name would be automatically included as a metadata in the future &#8211; it seems like a sensible feature to have always on. Or, at the very least, flip the checkbox around and require it to be checked if you want to <strong>exclude<\/strong> the tag name from the tag\u2019s metadata.<\/p>\n<p>At this point, you should have your monitor tag with almost all the settings in place, and you should have now edited all your tags to include the tag name. The monitor tag should have a Custom Event trigger that fires it for every single <code>dataLayer<\/code> event.<\/p>\n<div style=\"aspect-ratio: 514 \/ 216;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/preview-and-network.jpg\" title=\"Preview and network requests\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"216\" width=\"514\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/preview-and-network.jpg#ZgotmplZ\" alt=\"Preview and network requests\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Feel free to test in Preview mode to see if the endpoint is invoked with a proper query string. You should see something like this in the Network logs of your browser, assuming you used the placeholder URL of <code>https:\/\/placeholder.com\/collect<\/code>.<\/p>\n<p>Next up, <strong>Mark<\/strong> will show us how to get the Google Cloud Platform endpoint up and running!<\/p>\n<h2 id=\"step-3-creating-the-google-cloud-platform-endpoint\">Step 3: Creating the Google Cloud Platform endpoint<\/h2>\n<p>The GCP endpoint needs to process the <strong>GET requests<\/strong> sent from the site, insert them into a <strong>BigQuery table<\/strong>, parse them into columns and rows, and finally connect the data to <strong>Google Data Studio<\/strong>.<\/p>\n<h3 id=\"31-choosing-the-gcp-solution-for-the-endpoint\">3.1. Choosing the GCP solution for the endpoint<\/h3>\n<p>Boiling down the use case requirements here, we are looking to turn an API call containing the GTM tag data into useful data for analysis. There are many ways to solve this particular use case on GCP &#8211; we\u2019ll go through some and show why we eventually went for <a href=\"https:\/\/cloud.google.com\/functions\/\">Cloud Functions<\/a>.<\/p>\n<p>The general solution requirements were:<\/p>\n<ul>\n<li>\n<p><strong>Serverless<\/strong>: so we don\u2019t babysit a server but are able to deal with high traffic and\/or scale down to <em>zero cost<\/em> when no data is collected.<\/p>\n<\/li>\n<li>\n<p><strong>Low-code<\/strong>: easy to deploy and maintain.<\/p>\n<\/li>\n<li>\n<p><strong>Low-config<\/strong>: minimal maintenance so it can be generalized and can deal with varying data schemas easily.<\/p>\n<\/li>\n<\/ul>\n<p>The final output is to be a dashboard in Data Studio, so the natural choice for the eventual destination was BigQuery due to the ability to connect via its native Data Studio connector.<\/p>\n<blockquote>\n<p><strong>Quick aside<\/strong>: We are wary of dashboards being a final solution for data flows since they rely on a human being to react to give you ROI. It\u2019s fine for a proof-of-concept, but for your own project consider your actual use case &#8211; perhaps you could instead implement <strong>automatic alerts<\/strong> if certain thresholds on data quality were broken or something similar.<\/p>\n<\/blockquote>\n<p>With the above requirements in mind, some solutions included:<\/p>\n<h4 id=\"311-pixel-tracking-via-a-load-balancer\">3.1.1. Pixel tracking via a load balancer<\/h4>\n<p>We thought about a <a href=\"https:\/\/cloud.google.com\/solutions\/serverless-pixel-tracking\">Pixel Tracking Solution<\/a> first, since in theory the only steps needed are to upload a pixel to Cloud Storage, make it publicly available, and then stream the access logs into BigQuery.<\/p>\n<p>However the resulting API endpoint is an HTTP IP address (e.g. <code>http:\/\/111.222.333.444<\/code>) and the Google Tag Manager API requires communication over HTTPS. It\u2019s absolutely possible to assign a custom domain and SSL certificate to the load balancer, but to make the demo more accessible for everyone we wanted to find a solution that provided its own domain and HTTPS endpoint.<\/p>\n<p>However, if you do have a custom domain name at hand, the load balancer is probably the <strong>most robust solution<\/strong> to utilize, since it doesn\u2019t require the overhead of running a programmed application.<\/p>\n<h4 id=\"312-app-engine\">3.1.2. App Engine<\/h4>\n<p>The next stop was <a href=\"https:\/\/cloud.google.com\/appengine\/\">App Engine<\/a>, which up to last year would have been the one we\u2019d pursue. In fact, here is some code on how to set up <a href=\"https:\/\/github.com\/MarkEdmondson1234\/ga-bq-stream\">streaming from GTM to BigQuery<\/a> that Mark wrote three years ago.<\/p>\n<p>However, since then <strong>Cloud Functions<\/strong> have arrived which have even less configuration and code required to set up the service. You may still want to consider App Engine if you have more requirements such as integration with Cloud Endpoints or other GCP APIs, for which Cloud Functions do not have support yet.<\/p>\n<h4 id=\"313-technology-of-choice-cloud-functions\">3.1.3. Technology of choice: Cloud Functions<\/h4>\n<p>This is the one we went for. For getting something quickly up and running, it is hard to beat. If using Python, you only need to write code, create a <code>requirements.txt<\/code> file, set the function to trigger with a public endpoint (via HTTP requests), and you have everything we need &#8211; HTTPS, scalable infrastructure and almost no configuration necessary.<\/p>\n<div style=\"aspect-ratio: 955 \/ 230;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/website-to-bigquery.jpg\" title=\"Website to BigQuery\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"230\" width=\"955\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/website-to-bigquery.jpg#ZgotmplZ\" alt=\"Website to BigQuery\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It may cost more per call than the solutions above, but it scales down to zero meaning you can deploy and wait for traffic without incurring any costs for downtime.<\/p>\n<p>Note that Cloud Functions can take <a href=\"https:\/\/cloud.google.com\/functions\/quotas\">100 million calls per 100 seconds<\/a> in hits, but BigQuery itself accepts only (!) up to <a href=\"https:\/\/cloud.google.com\/bigquery\/quotas#streaming_inserts\">100,000 rows per second<\/a> per project which equates to 10 million calls per 100 seconds. If this is a limitation you might find yourself struggling with, you\u2019ll need to start batching the data to BigQuery instead of streaming it.<\/p>\n<h3 id=\"32-bigquery-setup\">3.2. BigQuery setup<\/h3>\n<p>With the API endpoint technology selected, we turn to configuring <a href=\"https:\/\/console.cloud.google.com\/bigquery\/\"><strong>BigQuery<\/strong><\/a>.<\/p>\n<p>Loading data into BigQuery requires a <strong>schema<\/strong> for the data (<code>STRING<\/code>, <code>INT<\/code>, <code>TIMESTAMP<\/code>, etc.) which can be tricky if you are sending in a lot of data with untidy types (most data, that is) &#8211; one mistake and the data won\u2019t load. We\u2019re accepting data from an open HTTP endpoint so we are almost certain to get messy data and thus want to be able to modify sent data without reconfiguring everything.<\/p>\n<p>To account for the above, we favor loading data into BigQuery with as few restrictions as possible, but then use BigQuery\u2019s SQL to parse or transform the raw data into tidier forms. If you don\u2019t know SQL but are better with JavaScript, you may prefer to do that data tidying logic in the Google Tag Manager code sending the data, or if better at Python perhaps in the Cloud Function collecting it. We still recommend to do this kind of data cleaning in BigQuery since otherwise changes need code updates in harder-to-reach places.<\/p>\n<p>To achieve this, the BigQuery table accepting the GTM data will have as simple a data schema as possible: it will accept the entire URL as a string and not try to parse out the tag names or GTM events quite yet. We shall then create a <a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/views\">BigQuery View<\/a> on top of that data which will split the URL into a workable, tidy format.<\/p>\n<div style=\"aspect-ratio: 589 \/ 208;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-schema.jpg\" title=\"bigquery schema\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"208\" width=\"589\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-schema.jpg#ZgotmplZ\" alt=\"bigquery schema\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The final raw data schema then is simply the <strong>URI<\/strong> and a <strong>timestamp<\/strong>. We consider a timestamp always useful to have, and we will stream the data into a <a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/partitioned-tables\">partitioned table<\/a> which automatically aligns the datasets with the date of data ingestion to make time-based analysis easier.<\/p>\n<p>To create the table, you need a Google Cloud Platform project with billing enabled. Once you have that in place, browse to <a href=\"https:\/\/console.cloud.google.com\/bigquery\">the BigQuery web UI<\/a> and create a dataset for your project.<\/p>\n<p>With the dataset in place, you can then create a table in it.<\/p>\n<p>Putting all of the above into place, the BigQuery table settings look like this:<\/p>\n<div style=\"aspect-ratio: 675 \/ 657;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-table-settings.jpg\" title=\"BigQuery table settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"657\" width=\"675\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-table-settings.jpg#ZgotmplZ\" alt=\"BigQuery table settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The partition will put each hit\u2019s data into a partition of today\u2019s date, e.g. <code>20190703<\/code>, which you can use to query particular time periods via BigQuery\u2019s <code>_PARTITIONDATE<\/code> field.<\/p>\n<p>Make note of the <strong>project<\/strong> name, <strong>dataset<\/strong> name, and <strong>table<\/strong> name. You are naturally free to choose whatever you want for these, but you need the values when configuring the Cloud Function code next.<\/p>\n<h3 id=\"33-the-cloud-function-code\">3.3. The Cloud Function Code<\/h3>\n<p>Now let us get some data into the table. This is the Python code we\u2019ll use to update the Cloud Function with. You don\u2019t need to store any code locally &#8211; you can use the <a href=\"https:\/\/console.cloud.google.com\/functions\/add\">web UI<\/a>. The code below is based on the examples given using the <a href=\"https:\/\/googlecloudplatform.github.io\/google-cloud-python\/latest\/bigquery\/index.html\">Python SDK for BigQuery<\/a>.<\/p>\n<p>See the next chapter for information on where to copy-paste this code.<\/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-python\" data-lang=\"python\"><span style=\"color:#00a\">from<\/span> <span style=\"color:#0aa;text-decoration:underline\">google.cloud<\/span> <span style=\"color:#00a\">import<\/span> bigquery\n<span style=\"color:#00a\">import<\/span> <span style=\"color:#0aa;text-decoration:underline\">datetime<\/span>\n<span style=\"color:#00a\">import<\/span> <span style=\"color:#0aa;text-decoration:underline\">logging<\/span>\n\nPROJECT=<span style=\"color:#a50\">'me-gtm-monitoring'<\/span> <span style=\"color:#aaa;font-style:italic\"># Update to match your project name<\/span>\nDATASET=<span style=\"color:#a50\">'gtm_monitoring'<\/span>    <span style=\"color:#aaa;font-style:italic\"># Update to match your dataset name<\/span>\nTABLE=<span style=\"color:#a50\">'raw_data'<\/span>            <span style=\"color:#aaa;font-style:italic\"># Update to match your table name<\/span>\n\n<span style=\"color:#00a\">def<\/span> <span style=\"color:#0a0\">stream_bq<\/span>(uri):\n\n    client = bigquery.Client()\n\n    table_ref = client.dataset(DATASET).table(TABLE)\n    table = client.get_table(table_ref)\n\n    <span style=\"color:#aaa;font-style:italic\"># Stream the URI of the request<\/span>\n    errors = client.insert_rows(table, [{<span style=\"color:#a50\">'URI'<\/span>:uri, \n                                         <span style=\"color:#a50\">'timestamp'<\/span>: datetime.datetime.now()}])\n\n    <span style=\"color:#00a\">if<\/span> errors:\n        logging.error(errors)\n\n\n<span style=\"color:#00a\">def<\/span> <span style=\"color:#0a0\">gtm_monitor<\/span>(request):\n\n    <span style=\"color:#00a\">if<\/span> request.url:\n        stream_bq(request.url)<\/code><\/pre>\n<\/div>\n<h4 id=\"331-code-walkthrough\">3.3.1. Code walkthrough<\/h4>\n<p>We first import the libraries for connecting to BigQuery (<code>google.cloud.bigquery<\/code>), creating the timestamp (<code>datetime<\/code>), and logging errors (<code>logging<\/code>).<\/p>\n<p>Then you need to alter the code to set the project, dataset, and table for the destination BigQuery data.<\/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-python\" data-lang=\"python\"><span style=\"color:#00a\">from<\/span> <span style=\"color:#0aa;text-decoration:underline\">google.cloud<\/span> <span style=\"color:#00a\">import<\/span> bigquery\n<span style=\"color:#00a\">import<\/span> <span style=\"color:#0aa;text-decoration:underline\">datetime<\/span>\n<span style=\"color:#00a\">import<\/span> <span style=\"color:#0aa;text-decoration:underline\">logging<\/span>\n\nPROJECT=<span style=\"color:#a50\">'your-project'<\/span> <span style=\"color:#aaa;font-style:italic\"># Update to match your project name<\/span>\nDATASET=<span style=\"color:#a50\">'your_dataset'<\/span> <span style=\"color:#aaa;font-style:italic\"># Update to match your dataset name<\/span>\nTABLE=<span style=\"color:#a50\">'your_table'<\/span>     <span style=\"color:#aaa;font-style:italic\"># Update to match your table name<\/span><\/code><\/pre>\n<\/div>\n<p>As you can see, you actually need to create the table first &#8211; you can see the settings for the table in the previous chapter. Once you have the project, dataset, and table created, update the Cloud Function code above with the correct values.<\/p>\n<p>Below is the function <code>stream_bq()<\/code> to stream data into BigQuery. As we are deploying the Cloud Function in the same project that hosts the BigQuery table, Cloud Functions will handle authentication for us. All we need is the <code>bigquery.Client()<\/code> call, and the Cloud Function will handle authentication and identity management using the default service account set for the project.<\/p>\n<p>We then create the table object, and stream the data into the table using the <code>client.insert_rows()<\/code> method. This will return errors if it fails, in which case we\u2019ll log them.<\/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-python\" data-lang=\"python\"><span style=\"color:#00a\">def<\/span> <span style=\"color:#0a0\">stream_bq<\/span>(uri):\n\n    client = bigquery.Client()\n\n    table_ref = client.dataset(DATASET).table(TABLE)\n    table = client.get_table(table_ref)\n\n    <span style=\"color:#aaa;font-style:italic\"># Stream the URI of the request<\/span>\n    errors = client.insert_rows(table, [{<span style=\"color:#a50\">'URI'<\/span>:uri, \n                                         <span style=\"color:#a50\">'timestamp'<\/span>: datetime.datetime.now()}])\n\n    <span style=\"color:#00a\">if<\/span> errors:\n        logging.error(errors)<\/code><\/pre>\n<\/div>\n<p>Next, <code>gtm_monitor()<\/code> is the main function that will be the <strong>entry point<\/strong> to the Cloud Function. We define this in the web UI (see below). The function checks if the request had a URL, and if it did, it sends this to the <code>stream_bq()<\/code> method described above.<\/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-python\" data-lang=\"python\"><span style=\"color:#00a\">def<\/span> <span style=\"color:#0a0\">gtm_monitor<\/span>(request):\n\n    <span style=\"color:#00a\">if<\/span> request.url:\n        stream_bq(request.url)<\/code><\/pre>\n<\/div>\n<p>Finally we also need to modify the <code>requirements.txt<\/code> file in the Cloud Function &#8211; we can do this in the web UI as well. Cloud Functions automatically parses this file to install any necessary python libraries through <code>pip<\/code>.<\/p>\n<p>The only external dependency we need is the BigQuery SDK. The beauty of Cloud Functions is that you don\u2019t need to install this locally and then deploy the whole project at once &#8211; you can simply edit the <code>requirements.txt<\/code> file in the online editor to have the Cloud Function automatically install the dependency before running any code.<\/p>\n<div style=\"aspect-ratio: 478 \/ 180;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/requirements-txt.jpg\" title=\"Requirements.txt\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"180\" width=\"478\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/requirements-txt.jpg#ZgotmplZ\" alt=\"Requirements.txt\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The line you need to add is 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-text\" data-lang=\"text\">google-cloud-bigquery==1.5.1<\/code><\/pre>\n<\/div>\n<h4 id=\"332-create-and-deploy-the-cloud-function\">3.3.2. Create and deploy the Cloud Function<\/h4>\n<p>With all the theory out of the way, we can now look how to actually write and deploy the code in the Cloud Function.<\/p>\n<div style=\"aspect-ratio: 494 \/ 389;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/create-gcf-setting.jpg\" title=\"Google Cloud Function creation\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"389\" width=\"494\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/create-gcf-setting.jpg#ZgotmplZ\" alt=\"Google Cloud Function creation\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<ol>\n<li>\n<p>From a Google Cloud Project (with <strong>billing enabled<\/strong>) go to <a href=\"https:\/\/console.cloud.google.com\/functions\/list\">https:\/\/console.cloud.google.com\/functions\/add<\/a>.<\/p>\n<\/li>\n<li>\n<p>Give the function a descriptive name.<\/p>\n<\/li>\n<li>\n<p>Select <strong>HTTP<\/strong> as the trigger.<\/p>\n<\/li>\n<li>\n<p>Tick the <code>Allow unauthenticated invocations<\/code> box to make it accessible from anywhere.<\/p>\n<\/li>\n<li>\n<p>Select the <code>Python 3.7<\/code> runtime.<\/p>\n<\/li>\n<li>\n<p>Paste in the main function code from the previous chapter into <code>main.py<\/code>, and make the appropriate changes where required (the project, dataset, and table names).<\/p>\n<\/li>\n<li>\n<p>Copy-paste the BigQuery dependency into the <code>requirements.txt<\/code> tab of the online editor.<\/p>\n<\/li>\n<li>\n<p>Set the <strong>Function to execute<\/strong> field to <code>gtm_monitor<\/code>, as this is the entry point of the Cloud Function.<\/p>\n<\/li>\n<li>\n<p>Hit <strong>Create<\/strong> at the bottom of the screen.<\/p>\n<\/li>\n<\/ol>\n<p>Note that you can expand the <strong>More<\/strong> link to change the region from <code>us-central-1<\/code> to something closer to where most of your traffic comes from. This should improve performance and decrease latency.<\/p>\n<p>After hitting <strong>Create<\/strong>, you should be able to see the URL the Cloud Function has assigned to your new function. This would be something like <code>https:\/\/us-central1-my-project.cloudfunctions.net\/my-function<\/code>.<\/p>\n<p>After all that you should have a function that looks a little like this:<\/p>\n<div style=\"aspect-ratio: 474 \/ 396;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/cloud-function-done-1.jpg\" title=\"Cloud function done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"396\" width=\"474\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/cloud-function-done-1.jpg#ZgotmplZ\" alt=\"Cloud function done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<div style=\"aspect-ratio: 566 \/ 495;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/cloud-function-done-2.jpg\" title=\"Cloud function done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"495\" width=\"566\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/cloud-function-done-2.jpg#ZgotmplZ\" alt=\"Cloud function done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"34-update-the-google-tag-manager-monitor-tag\">3.4. Update the Google Tag Manager Monitor tag<\/h3>\n<p>With your cloud function deployed, you should now have a URL endpoint for your Google Tag Manager Monitor tag looking something like <code>https:\/\/us-central1-your-project.cloudfunctions.net\/your-function<\/code>.<\/p>\n<div style=\"aspect-ratio: 517 \/ 265;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/get-request-endpoint.jpg\" title=\"Get request endpoint\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"265\" width=\"517\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/get-request-endpoint.jpg#ZgotmplZ\" alt=\"Get request endpoint\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In your Google Tag Manager Monitor tag, update the <strong>GET request endpoint<\/strong> to reflect the correct Cloud Function HTTP endpoint and save the tag. You\u2019ll then need to <strong>edit the Custom Template<\/strong> and open its <strong>Permissions<\/strong> tab. Update the <strong>Sends Pixels<\/strong> permission with the correct endpoint URL.<\/p>\n<div style=\"aspect-ratio: 639 \/ 175;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/update-permission.jpg\" title=\"Update permission\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"175\" width=\"639\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/update-permission.jpg#ZgotmplZ\" alt=\"Update permission\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Make sure to add the <code>*<\/code> at the end of the permission URL to account for query parameters.<\/p>\n<p>You can continue in <strong>Preview mode<\/strong> for a while to ensure that everything works before <strong>publishing<\/strong> the container for all your visitors.<\/p>\n<p>If everything works, you should see GCP logs of your Cloud Function (accessible in the Cloud Function UI) showing HTTP calls are being triggered.<\/p>\n<div style=\"aspect-ratio: 938 \/ 191;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/view-gcp-logs.jpg\" title=\"View GCP Logs\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"191\" width=\"938\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/view-gcp-logs.jpg#ZgotmplZ\" alt=\"View GCP Logs\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Within BigQuery, you should see data is being populated with the URI requested. Make sure to turn the <strong>cache<\/strong> off and query via <code>SELECT * FROM dataset.table_name<\/code> to test live data, as the Preview may show no data even when there is some. Nevertheless, after doing the <code>SELECT *<\/code> query, or after patiently waiting for the Preview to update, you should see your raw data being logged into BigQuery like this:<\/p>\n<div style=\"aspect-ratio: 824 \/ 400;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-preview-raw.jpg\" title=\"BigQuery Preview Raw\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"400\" width=\"824\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/bigquery-preview-raw.jpg#ZgotmplZ\" alt=\"BigQuery Preview Raw\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you got this far, congratulations! You now have a live stream of data from your Google Tag Manager setup into BigQuery. Just imagine all the other use cases for this pipeline!<\/p>\n<h3 id=\"35-parsing-bigquery-raw-data-into-a-view\">3.5. Parsing BigQuery raw data into a view<\/h3>\n<p>Now we do the parsing of this data into something useful. Simo took a jab at writing the necessary SQL for the proof-of-concept (M: Good job, Simo! S: Why, thank you!).<\/p>\n<p>There is probably some more elegant way to do this with arrays, but this is what we went with in that it uses regular expressions to parse out the URI string into usable columns. It is here you can do adjustments to the output data to put it in a format you want.<\/p>\n<p>Make sure to update the <code>FROM<\/code> statement to pull the data from the correct project\/dataset\/table combination.<\/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-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">timestamp<\/span>,\n  eventName,\n  eventTimestamp,\n  (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(tagString, <span style=\"color:#a50\">\"&amp;tag\\\\d+id=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> tagId,\n  (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(tagString, <span style=\"color:#a50\">\"&amp;tag\\\\d+nm=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> tagName,\n  (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(tagString, <span style=\"color:#a50\">\"&amp;tag\\\\d+st=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> tagStatus,\n  (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(tagString, <span style=\"color:#a50\">\"&amp;tag\\\\d+et=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> tagExecutionTime\n<span style=\"color:#00a\">FROM<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>\n    <span style=\"color:#00a\">timestamp<\/span>,\n    eventName,\n    eventTimestamp,\n    tagStringUnnest <span style=\"color:#00a\">as<\/span> tagString,\n    pt\n  <span style=\"color:#00a\">FROM<\/span> (\n    <span style=\"color:#00a\">SELECT<\/span> \n      <span style=\"color:#00a\">timestamp<\/span>,\n      (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(URI, <span style=\"color:#a50\">\"eventName=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> eventName,\n      (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT(URI, <span style=\"color:#a50\">\"eventTimestamp=([^&amp;]+)\"<\/span>)) <span style=\"color:#00a\">as<\/span> eventTimestamp,\n      (<span style=\"color:#00a\">SELECT<\/span> REGEXP_EXTRACT_ALL(URI, <span style=\"color:#a50\">\"&amp;tag\\\\d+id=[^&amp;]+&amp;tag\\\\d+nm=[^&amp;]+&amp;tag\\\\d+st=[^&amp;]+&amp;tag\\\\d+et=[^&amp;]+\"<\/span>)) <span style=\"color:#00a\">as<\/span> tagStringRegex,\n      <span style=\"color:#0aa\">DATE<\/span>(_PARTITIONTIME) <span style=\"color:#00a\">as<\/span> pt\n    <span style=\"color:#00a\">FROM<\/span>\n      `your-project.gtm_monitoring.raw_data`\n  )\n  <span style=\"color:#00a\">LEFT<\/span> <span style=\"color:#00a\">JOIN<\/span> <span style=\"color:#00a\">UNNEST<\/span>(tagStringRegex) <span style=\"color:#00a\">as<\/span> tagStringUnnest\n)<\/code><\/pre>\n<\/div>\n<p>Once you have the SQL you want running in the <a href=\"https:\/\/console.cloud.google.com\/bigquery\">BigQuery Web UI<\/a>, select <strong>Save View<\/strong> to save the query as a table that will be used day-to-day.<\/p>\n<div style=\"aspect-ratio: 729 \/ 393;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/save-query-view.jpg\" title=\"Save query view\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"393\" width=\"729\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/save-query-view.jpg#ZgotmplZ\" alt=\"Save query view\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Here\u2019s what the data looks like when parsed into the view.<\/p>\n<div style=\"aspect-ratio: 923 \/ 778;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/query-the-view.jpg\" title=\"Query the view\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"778\" width=\"923\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/query-the-view.jpg#ZgotmplZ\" alt=\"Query the view\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"step-4-visualize-the-data\">Step 4: Visualize the data<\/h2>\n<p>For most use cases, having the data in a BigQuery view is already more than enough. That\u2019s the data set you can query for anomaly detection, or plug into a pipeline for automated alerts.<\/p>\n<p>Nevertheless, we can see how useful a proper visualization of the data could potentially be. For this purpose, Google\u2019s <a href=\"https:\/\/datastudio.google.com\/\">Data Studio<\/a> is an excellent project for quickly delivering useful visualizations. Best of all, it already has a built-in <a href=\"https:\/\/support.google.com\/datastudio\/answer\/6370296?hl=en\">BigQuery connector<\/a> for querying your BQ data with.<\/p>\n<div style=\"aspect-ratio: 1200 \/ 486;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/07\/data-studio-dashboard.jpg\" title=\"Data Studio Dashboard\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"486\" width=\"1200\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/07\/data-studio-dashboard.jpg#ZgotmplZ\" alt=\"Data Studio Dashboard\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As a demo, we\u2019ve created a <strong>starter<\/strong> dashboard for you to check out. Feel free to copy it and modify it to suit your own purposes.<\/p>\n<p><a href=\"https:\/\/datastudio.google.com\/u\/0\/reporting\/1qcyKjq_44_VIeIxL4BTQm6kKnBaH5a7p\/page\/xsws\">Visit the dashboard here.<\/a><\/p>\n<h2 id=\"summary\">Summary<\/h2>\n<p>Maybe we haven\u2019t said this enough yet, but this is a <strong>proof-of-concept<\/strong>. This article serves a number of purposes:<\/p>\n<ul>\n<li>\n<p>You\u2019ll learn how to set up a pipeline of data collection from the website, via Google Tag Manager, to a data warehouse located in the Google Cloud.<\/p>\n<\/li>\n<li>\n<p>You\u2019ll learn about the new <code>addEventCallback<\/code> API in Google Tag Manager\u2019s custom templates.<\/p>\n<\/li>\n<li>\n<p>You\u2019ll learn about the new <strong>Additional Tag Metadata<\/strong> field in GTM\u2019s tags.<\/p>\n<\/li>\n<li>\n<p>You\u2019ll learn how to efficiently monitor the success rates and execution times of your Google Tag Manager tags.<\/p>\n<\/li>\n<\/ul>\n<p>We can only hope that others build much cooler and more useful solutions than this simple MVP. Some features we considered useful but not in the scope of this introductory article:<\/p>\n<ol>\n<li>\n<p>Report that compares data from the respective end points with that of GTM\u2019s monitor system to see if tags are firing but not sending the correct data.<\/p>\n<\/li>\n<li>\n<p>Utilize a pixel file stored in Google Cloud Storage, behind a load balancer, to achieve a zero-code setup with superior performance compared to a Cloud Function.<\/p>\n<\/li>\n<li>\n<p>Training a machine learning model on the data set to identify anomalies and automatically alert to them when detected.<\/p>\n<\/li>\n<\/ol>\n<p>Let us know in the comments what you think of this, and how you would improve it if you had the chance!<\/p>\n<p><strong>Final note from Simo<\/strong>: I am truly grateful to get the great Mark Edmondson to co-author this article and design the monitor solution with me. As the editor, I take full responsibility for any errors and mistakes in the final draft of the article.<\/p>\n<\/p><\/div>\n<p><script async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><br \/>\n<br \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Google Tag Manager is strictly a tag delivery system, and it\u2019s very careful not to collect any analytics data on its own. This is most likely a deliberate choice, because if GTM was to start collecting data, it would introduce additional barriers to adoption. Nevertheless, being a tool that consolidates the design, development, deployment, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":98075,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[5293,1531,14495,3553,1791],"dealstore":[],"offerexpiration":[],"class_list":["post-98074","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-build","tag-google","tag-manager","tag-monitor","tag-tag"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How To Build A Google Tag Manager Monitor - 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=98074\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How To Build A Google Tag Manager Monitor - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Google Tag Manager is strictly a tag delivery system, and it\u2019s very careful not to collect any analytics data on its own. This is most likely a deliberate choice, because if GTM was to start collecting data, it would introduce additional barriers to adoption. Nevertheless, being a tool that consolidates the design, development, deployment, and [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=98074\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-19T20:38:29+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1152\" \/>\n\t<meta property=\"og:image:height\" content=\"395\" \/>\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=\"27 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=98074#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"How To Build A Google Tag Manager Monitor\",\"datePublished\":\"2025-02-19T20:38:29+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074\"},\"wordCount\":4643,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg\",\"keywords\":[\"Build\",\"Google\",\"Manager\",\"Monitor\",\"TAG\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=98074#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=98074\",\"url\":\"https:\/\/fivemor.com\/?p=98074\",\"name\":\"How To Build A Google Tag Manager Monitor - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg\",\"datePublished\":\"2025-02-19T20:38:29+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=98074#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=98074\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=98074#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg\",\"width\":1152,\"height\":395},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=98074#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How To Build A Google Tag Manager Monitor\"}]},{\"@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":"How To Build A Google Tag Manager Monitor - 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=98074","og_locale":"en_US","og_type":"article","og_title":"How To Build A Google Tag Manager Monitor - Som2ny Network","og_description":"Google Tag Manager is strictly a tag delivery system, and it\u2019s very careful not to collect any analytics data on its own. This is most likely a deliberate choice, because if GTM was to start collecting data, it would introduce additional barriers to adoption. Nevertheless, being a tool that consolidates the design, development, deployment, and [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=98074","og_site_name":"Som2ny Network","article_published_time":"2025-02-19T20:38:29+00:00","og_image":[{"width":1152,"height":395,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"27 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=98074#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=98074"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"How To Build A Google Tag Manager Monitor","datePublished":"2025-02-19T20:38:29+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=98074"},"wordCount":4643,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=98074#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg","keywords":["Build","Google","Manager","Monitor","TAG"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=98074#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=98074","url":"https:\/\/fivemor.com\/?p=98074","name":"How To Build A Google Tag Manager Monitor - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=98074#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=98074#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg","datePublished":"2025-02-19T20:38:29+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=98074#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=98074"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=98074#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/1739997510_google-tag-manager-monitor.jpg","width":1152,"height":395},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=98074#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"How To Build A Google Tag Manager Monitor"}]},{"@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\/98074","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=98074"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/98074\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/98075"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=98074"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=98074"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=98074"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=98074"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=98074"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}