{"id":9748,"date":"2026-08-16T12:58:48","date_gmt":"2026-08-16T10:58:48","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=9748"},"modified":"2026-08-13T14:30:55","modified_gmt":"2026-08-13T12:30:55","slug":"gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/","title":{"rendered":"gtag(&#8216;set&#8217;) in practice: handing a User ID to a Google tag that has already loaded"},"content":{"rendered":"<p>A signed-in web application usually learns who the visitor is a moment too late. The page loads, the Google tag initialises, the <code>config<\/code> command sends its <code>page_view<\/code> \u2014 and only afterwards does the login form return a session, or the single-page application resolve the account from an API call. By the time the User ID exists, the first hit has already left the browser.<\/p>\n<p>The reflex in that situation is to configure the tag a second time with the identifier attached. That reflex is expensive and unnecessary. An already loaded Google tag does not have to be reloaded or re-initialised because one value arrived late: a single <code>set<\/code> command hands the value over, and every event sent afterwards on that page carries it.<\/p>\n<figure class=\"lw-diagram\">\n<img loading=\"lazy\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/hand-gtagset-en.png\" width=\"1120\" height=\"620\" decoding=\"async\"\n     alt=\"Two time lanes compared: firing the Google tag a second time produces a duplicate page_view, whereas a single set command passes the User ID to the running tag\"><figcaption>The same moment, two routes: firing the Google tag again re-initialises it and writes a second page_view into the session, while one set command hands the User ID to the tag that is already running and every following event carries it.<\/figcaption><\/figure>\n<h2>The five gtag commands and how their scopes interact<\/h2>\n<p>The gtag.js API knows exactly five commands, and their division of labour is what makes the late hand-over possible.<\/p>\n<table style=\"width:100%;border-collapse:collapse;table-layout:auto;\">\n<thead>\n<tr>\n<th style=\"white-space:nowrap;vertical-align:top;\">Command<\/th>\n<th>Signature<\/th>\n<th>Purpose<\/th>\n<th>Reach<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top;\"><code>config<\/code><\/td>\n<td><code>gtag('config', '&lt;TARGET_ID&gt;', {\u2026})<\/code><\/td>\n<td>Adds configuration information to a target; the TARGET_ID decides where gtag.js sends event data, and a further call adds a further destination.<\/td>\n<td>All events to that one TARGET_ID.<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top;\"><code>set<\/code><\/td>\n<td><code>gtag('set', {\u2026})<\/code><\/td>\n<td>Defines parameters associated with every subsequent event on the page. A named group form exists too, for example <code>gtag('set', 'campaign', {\u2026})<\/code>.<\/td>\n<td>Global, from the call onwards, current page only.<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top;\"><code>event<\/code><\/td>\n<td><code>gtag('event', '&lt;event_name&gt;', {\u2026})<\/code><\/td>\n<td>Sends event data, either a recommended event or a custom one.<\/td>\n<td>That single event.<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top;\"><code>get<\/code><\/td>\n<td><code>gtag('get', '&lt;target&gt;', '&lt;field&gt;', callback)<\/code><\/td>\n<td>Reads values back out of gtag.js, including values assigned with <code>set<\/code>: <code>client_id<\/code>, <code>session_id<\/code>, <code>session_number<\/code>, <code>gclid<\/code>.<\/td>\n<td>Read access; the callback receives the field or <code>undefined<\/code>.<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top;\"><code>consent<\/code><\/td>\n<td><code>gtag('consent', 'default'|'update', {\u2026})<\/code><\/td>\n<td>Sets the initial consent state, or updates it once a decision exists.<\/td>\n<td>Consent signals for the page.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Three scopes therefore exist: individual events, all events to a specific TARGET_ID, and globally all events. Where the same parameter is assigned in more than one of them, only a single value is used when an event is processed, and the order is <strong>event before config before set<\/strong>. The decisive detail sits one line further down in the reference: precedence is not overwriting.<\/p>\n<blockquote>\n<p>Parameter values set in one scope don&#8217;t modify the values set for the same parameter in a different scope.<\/p>\n<\/blockquote>\n<p>In the documented example <code>campaign_id<\/code> is assigned globally as <code>'1234'<\/code> and then as <code>'ABCD'<\/code> in a <code>config<\/code> call; afterwards the global value is still <code>'1234'<\/code>. The scopes keep their values side by side, and the ranking decides only at processing time which applies. Combining the two rules yields a consequence the documentation does not spell out: a <code>set<\/code> call issued after <code>config<\/code> does not displace a value <code>config<\/code> already occupies for that target, and takes effect for the parameters <code>config<\/code> left untouched.<\/p>\n<p>On repetition the documentation is explicit: <code>config<\/code> is to be called once per Google tag, every subsequent call should use <code>set<\/code>. It also recommends <code>config<\/code> or <code>event<\/code> over <code>set<\/code> wherever possible, which concerns custom event parameters in particular, because <code>set<\/code> may not propagate them reliably to all Google Analytics measurement streams. For documented global parameters such as <code>user_id<\/code>, <code>page_title<\/code> or <code>currency<\/code>, <code>set<\/code> is the intended instrument.<\/p>\n<h2>The case in detail: handing over the User ID late<\/h2>\n<p>User IDs are self-assigned identifiers for individual users; assigning them consistently is the operator&#8217;s business, normally at login. Where the identifier is already known at render time, it can travel inside the <code>config<\/code> call. Where it becomes known only after the page has loaded, <code>gtag('set')<\/code> is the documented route. The page snippet stays exactly as installed.<\/p>\n<pre class=\"wp-block-code\"><code>&lt;script async src=\"https:\/\/www.googletagmanager.com\/gtag\/js?id=G-XXXXXXXXXX\"&gt;&lt;\/script&gt;\n&lt;script&gt;\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n  gtag('config', 'G-XXXXXXXXXX');\n&lt;\/script&gt;<\/code><\/pre>\n<p>Once the login response arrives, one command is enough. No second <code>config<\/code>, no second snippet, no reload.<\/p>\n<pre class=\"wp-block-code\"><code>fetch('\/api\/session')\n  .then(function (response) { return response.json(); })\n  .then(function (session) {\n    if (session.analyticsId) {\n      gtag('set', { 'user_id': session.analyticsId });\n    }\n    gtag('event', 'login', { 'method': 'password' });\n  });<\/code><\/pre>\n<p>What happens next is split between the browser and the processing side. In the browser, the effect starts at the moment of the call and runs to the end of the page. Every event dispatched after the <code>set<\/code> command carries <code>user_id<\/code>; every event dispatched before it does not. The phrase in the reference \u2014 parameters associated with every subsequent event on the page \u2014 limits the effect forwards in time and to the current document. A classic multi-page site therefore re-issues the command on each page load; a single-page application issues it once for the lifetime of the document.<\/p>\n<p>On the processing side, the earlier events are not lost. Analytics associates events that fired before the User ID was set with that User ID. The documented example runs through a session that starts signed out: event 1 and event 2 carry no identifier, the user signs in, event 3 follows \u2014 and afterwards events 1, 2 and 3 are all associated with that user&#8217;s ID. Two boundaries apply. The association is described within a session, and it does not reach back into data collected before the implementation existed; such data is not reprocessed. Whether it extends to earlier, already closed sessions on the same device is not stated in the primary sources.<\/p>\n<p>Sign-out is the mirror image. The value is set to <code>null<\/code> \u2014 not an empty string, not a space, not the string <code>\"null\"<\/code>.<\/p>\n<pre class=\"wp-block-code\"><code>gtag('set', { 'user_id': null });<\/code><\/pre>\n<p>Analytics then stops associating subsequent events with that User ID: in the documented example, events 1 to 3 remain associated and event 4 does not. A renewed sign-in with the same identifier within the same session resumes the association.<\/p>\n<h2>The update parameter: merging instead of re-initialising<\/h2>\n<p>The <code>set<\/code> command answers one question: which value should travel<br \/>\nwith everything the page sends from now on. A second, narrower instrument<br \/>\nanswers a different one: how an existing configuration is changed without the<br \/>\nside effects of a fresh <code>config<\/code> call. That instrument is the<br \/>\n<code>update<\/code> parameter, passed inside <code>config<\/code> itself.<\/p>\n<blockquote>\n<p>If set to <code>true<\/code>, the <code>update<\/code> parameter merges new or updated parameters into the existing Google tag configuration but does not send a <code>page_view<\/code> event for that update. This is typically used in single-page applications to update parameters without re-initializing the tag.<\/p>\n<\/blockquote>\n<p>The field is a boolean and defaults to <code>false<\/code>. That default is<br \/>\nprecisely why a repeated <code>config<\/code> call produces the duplicate hit<br \/>\ndescribed above: without <code>update<\/code>, the call is treated as a fresh<br \/>\nconfiguration, and a fresh configuration sends a <code>page_view<\/code>.<\/p>\n<pre class=\"wp-block-code\"><code>gtag('config', 'G-XXXXXXXXXX', {\n  'update': true,\n  'page_location': 'https:\/\/example.com\/new-page'\n});<\/code><\/pre>\n<p>Two differences separate this from <code>set<\/code>. The first is reach:<br \/>\n<code>update<\/code> merges into the configuration of the one target it names,<br \/>\nwhile <code>set<\/code> assigns globally for every subsequent event and every<br \/>\ndestination. The second is intent: <code>update<\/code> revises what the tag<br \/>\nalready holds, whereas <code>set<\/code> adds a value on top without touching<br \/>\nwhat <code>config<\/code> occupies.<\/p>\n<p>That makes the choice reasonably clear. A User ID arriving after login<br \/>\nbelongs in <code>set<\/code> \u2014 it is a global property of everything that<br \/>\nfollows. A route change in a single-page application belongs in<br \/>\n<code>config<\/code> with <code>update<\/code> set to <code>true<\/code>, because<br \/>\n<code>page_location<\/code> and <code>page_title<\/code> are configuration of that<br \/>\none stream, and the accompanying <code>page_view<\/code> is better sent<br \/>\ndeliberately than as a by-product.<\/p>\n<h2>The Google Tag Manager route<\/h2>\n<p>Tag Manager and gtag.js share a single global <code>window.dataLayer<\/code>; only one such object is supported per page, and <code>gtag()<\/code> is a wrapper that pushes its arguments into it. A Google tag deployed through GTM also processes on-page <code>gtag()<\/code> commands in addition to the settings configured in the interface. For common context across several Google tags, the GTM help page names gtag.js on the website itself \u2014 with the caveat that global parameters are read by all Google tags on the site and should carry non-sensitive data only.<\/p>\n<p>What the GTM interface does not offer is a ready-made tag type for a late <code>set<\/code>. The <em>Configuration settings<\/em> variable sets parameters loaded when the Google tag loads \u2014 a load-time mechanism; the <em>Event settings<\/em> variable adds parameters per event. Neither of the two updates a Google tag that is already running. The container-side equivalent sits one level below the interface, in the Custom Templates API: <code>gtagSet(object)<\/code> pushes a gtag set command into the data layer, to be processed as soon as the current event and the tags it triggered have finished, and the reference guarantees that this update is handled in the container before any items already queued in the data layer. The route is therefore documented; what is missing is only a preconfigured tag type for it, so the update runs through a template of one&#8217;s own. A packaged tag with that behaviour is not described in the documentation reviewed here, which is a negative finding rather than an explicit exclusion by Google.<\/p>\n<p>The queue is what separates the two paths. Tag Manager processes messages first in, first out, one at a time in the order received. A <code>gtag()<\/code> or <code>dataLayer.push()<\/code> call from page code or from a Custom HTML tag is queued behind all pending messages, and the updated values are not guaranteed to be available for the next event; the same applies to a generic data layer push out of template code. The two dedicated template APIs are the documented exception: <code>gtagSet<\/code> and its sibling <code>updateConsentState<\/code> are guaranteed to be processed in the container ahead of anything waiting in the data layer queue. The constraint is thus not the container as such but the path chosen through it \u2014 a raw push carries no ordering guarantee, the template API does.<\/p>\n<p>Re-firing the Google tag to inject the identifier is the wrong lever for a second reason. The <code>config<\/code> command initiates a pageview event in some products, in Google Analytics it sends <code>page_view<\/code> when the tag loads, and configuring the same Google tag twice on one page may cause duplicate data or mixed settings. That a second firing of the GTM Google tag produces a second <code>page_view<\/code> is a reasonable inference from these pieces, not a statement Google makes for that tag type. Suppression is possible with <code>send_page_view: false<\/code> \u2014 in the snippet, globally via <code>set<\/code>, or in the Configuration settings variable \u2014 but that setting does not carry across pages.<\/p>\n<p>An announcement dated 20 May 2026 belongs in the picture: new deployment snippets will be uniform and will drop the <code>gtag<\/code> config command, with initialisation configured through a <em>gtm init<\/em> trigger that can also wait for the config command to preserve a legacy setup. Nothing changes automatically, in-page behaviour is described as unaffected, and the install example in the developer documentation still shows <code>gtag('config', 'TAG_ID');<\/code>.<\/p>\n<h2>The sibling case: Consent Mode<\/h2>\n<p>Consent Mode follows the same shape \u2014 a value unknown when the tag loads, handed over later without reloading anything \u2014 with a different target and stricter ordering. By default no consent mode values are set. The <code>default<\/code> command must run on every page before any command that sends measurement data; called out of order, the defaults do not work.<\/p>\n<pre class=\"wp-block-code\"><code>gtag('consent', 'default', {\n  'ad_storage': 'denied',\n  'ad_user_data': 'denied',\n  'ad_personalization': 'denied',\n  'analytics_storage': 'denied',\n  'wait_for_update': 500\n});<\/code><\/pre>\n<p>The <code>wait_for_update<\/code> value in milliseconds controls how long the tag waits before sending data, which matters for consent solutions that load asynchronously; where network requests are involved, at least 500 milliseconds is suggested. Defaults can be differentiated by region per ISO 3166-2, the more specific region winning.<\/p>\n<p>The update happens on the page where the decision is made, before any page transition, because Consent Mode does not store the choice itself.<\/p>\n<pre class=\"wp-block-code\"><code>gtag('consent', 'update', {\n  'ad_storage': 'granted',\n  'ad_user_data': 'granted',\n  'ad_personalization': 'granted',\n  'analytics_storage': 'granted'\n});<\/code><\/pre>\n<p>No reload is required, and the state change itself produces a signal: consent state pings are sent from each page where consent mode is enabled and are additionally triggered for some tags when the state changes from denied to granted. In the advanced implementation the tags load immediately and, while consent is denied, send measurements without cookies. Inside Tag Manager templates the gtag consent command is explicitly not to be used \u2014 the <code>updateConsentState<\/code> API exists so that consent updates are processed ahead of queued data layer items, the same ordering guarantee <code>gtagSet<\/code> carries for parameters.<\/p>\n<h2>Pitfalls and checks<\/h2>\n<ul>\n<li><strong>Scope, not overwrite.<\/strong> A late <code>set<\/code> does not rewrite what <code>config<\/code> or an event-level parameter already holds. Where a value must win, it belongs in the higher-ranking scope.<\/li>\n<li><strong>Page boundaries and ordering.<\/strong> <code>set<\/code> reaches forward only, within the current document; server-rendered navigations need the command again. The snippet must appear above the event commands, and <code>set<\/code> belongs above <code>config<\/code> where several tag IDs should inherit the values.<\/li>\n<li><strong>Container path.<\/strong> Inside GTM the ordering guarantee belongs to the template APIs. A late value pushed from a Custom HTML tag competes with the queue; the same value handed over through <code>gtagSet<\/code> in a custom template does not.<\/li>\n<li><strong>Identifier hygiene.<\/strong> A User ID value is limited to 256 characters, UTF-8 only per the Measurement Protocol reference, and must not contain information a third party could use to determine a user&#8217;s identity; email addresses and personal mobile numbers are named as impermissible. Whether a hashed derivation qualifies cannot be settled from the primary sources.<\/li>\n<li><strong>No custom dimension on the ID.<\/strong> Registering <code>user_id<\/code> as a custom dimension is advised against because of the very high number of distinct values.<\/li>\n<li><strong>Reporting identity.<\/strong> Device-based reporting shows no User-ID analysis; Blended or Observed is required. The choice affects neither collection nor processing.<\/li>\n<li><strong>Verification.<\/strong> <code>gtag('get', \u2026)<\/code> reads back <code>client_id<\/code>, <code>session_id<\/code>, <code>session_number<\/code> and <code>gclid<\/code>, and a value assigned to one of those fields via <code>set<\/code> takes precedence over the internally derived one. Tag Assistant reports consent in separate <em>On-page Default<\/em> and <em>On-page Update<\/em> columns.<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>The trade-off is lopsided. Re-configuring a loaded Google tag to attach a value costs a duplicate initialisation, risks mixed settings and a second <code>page_view<\/code>, and buys nothing a single <code>set<\/code> command does not deliver. For the User ID the accounting is clearer still, because the processing side closes the gap the browser leaves open: events already sent within the session are associated with the identifier afterwards, so the hits before the login are not sacrificed.<\/p>\n<p>What the approach does not do is cross boundaries it was never given. It does not survive a page transition, it does not outrank a value already held by a higher scope, and it does not reach back into data collected before the implementation existed. Consent Mode shows the same pattern from another angle: a documented default, a later update on the same page, a state change that reaches the tags without a reload. Where a container manages the tag, the queue is the constraint to design around, and <code>gtagSet<\/code> together with <code>updateConsentState<\/code> is the documented exemption from it \u2014 at the price of a custom template. The announced unification of Google tag and Tag Manager, still in the future tense in August 2026, is worth tracking rather than pre-empting.<\/p>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/developers.google.com\/tag-platform\/gtagjs\/reference\" target=\"_blank\" rel=\"noopener noreferrer\">gtag.js reference<\/a><\/li>\n<li><a href=\"https:\/\/developers.google.com\/tag-platform\/gtagjs\/configure\" target=\"_blank\" rel=\"noopener noreferrer\">Google tag: gtag.js configuration<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>A late-arriving User ID does not require a second config call: one gtag(&#8216;set&#8217;) command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.<\/p>\n","protected":false},"author":1,"featured_media":9751,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[91258,91267,91312,91516,91252],"class_list":["post-9748","post","type-post","status-publish","format-standard","hentry","category-digital-analytics","tag-google-analytics-4","tag-google-tag-manager","tag-javascript","tag-tracking","tag-web-analytics"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>gtag(&#039;set&#039;) in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog<\/title>\n<meta name=\"description\" content=\"A late-arriving User ID does not require a second config call: one gtag(&#039;set&#039;) command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"gtag(&#039;set&#039;) in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"A late-arriving User ID does not require a second config call: one gtag(&#039;set&#039;) command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-16T10:58:48+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"luky\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"luky\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/\"},\"author\":{\"name\":\"luky\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"gtag(&#8216;set&#8217;) in practice: handing a User ID to a Google tag that has already loaded\",\"datePublished\":\"2026-08-16T10:58:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/\"},\"wordCount\":2307,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg\",\"keywords\":[\"Google Analytics 4\",\"Google Tag Manager\",\"JavaScript\",\"Tracking\",\"Web Analytics\"],\"articleSection\":[\"Digital Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/\",\"name\":\"gtag('set') in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg\",\"datePublished\":\"2026-08-16T10:58:48+00:00\",\"description\":\"A late-arriving User ID does not require a second config call: one gtag('set') command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg\",\"width\":1200,\"height\":630,\"caption\":\"gtag('set') in practice: handing a User ID to a Google tag that has already loaded\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"gtag(&#8216;set&#8217;) in practice: handing a User ID to a Google tag that has already loaded\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"],\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/author\\\/luky\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"gtag('set') in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog","description":"A late-arriving User ID does not require a second config call: one gtag('set') command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.","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:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/","og_locale":"en_US","og_type":"article","og_title":"gtag('set') in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog","og_description":"A late-arriving User ID does not require a second config call: one gtag('set') command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-08-16T10:58:48+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg","type":"image\/jpeg"}],"author":"luky","twitter_card":"summary_large_image","twitter_misc":{"Written by":"luky","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/"},"author":{"name":"luky","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"gtag(&#8216;set&#8217;) in practice: handing a User ID to a Google tag that has already loaded","datePublished":"2026-08-16T10:58:48+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/"},"wordCount":2307,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg","keywords":["Google Analytics 4","Google Tag Manager","JavaScript","Tracking","Web Analytics"],"articleSection":["Digital Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/","name":"gtag('set') in practice: handing a User ID to a Google tag that has already loaded - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg","datePublished":"2026-08-16T10:58:48+00:00","description":"A late-arriving User ID does not require a second config call: one gtag('set') command hands the value to a Google tag that is already running, and every following event on the page carries it. The article covers scope precedence, the Tag Manager route including the gtagSet template API, and the parallel case of Consent Mode.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/fi-9748-gtag-set-in-practice-handing-a-user-id-to-a-google-t.jpg","width":1200,"height":630,"caption":"gtag('set') in practice: handing a User ID to a Google tag that has already loaded"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/gtagset-in-practice-handing-a-user-id-to-a-google-tag-that-has-already-loaded\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"gtag(&#8216;set&#8217;) in practice: handing a User ID to a Google tag that has already loaded"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"],"url":"https:\/\/www.lukaswojcik.com\/blog\/author\/luky\/"}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/9748","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=9748"}],"version-history":[{"count":2,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/9748\/revisions"}],"predecessor-version":[{"id":10069,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/9748\/revisions\/10069"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/9751"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=9748"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=9748"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=9748"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}