UTM & URL Query String 2 Cookies
UTM & URL Query String to Cookies in Google Tag Manager — A Complete Recipe Without Custom JavaScript
Persist any URL query string parameter — UTM values, click IDs, or custom campaign codes — into session or long-lived persistent cookies using only GTM sandboxed Custom Templates. No Custom HTML tags. No new CSP hashes.
UTM parameters tell you exactly how a visitor arrived at your site. But they only exist in the URL on the landing page. The moment a user navigates to a second page, clicks an internal link, or returns to the site in the same browser session, those parameters vanish from the address bar. If your marketing team needs to know which campaign drove a lead form submission that happened three days after the original visit — a common scenario in B2B with longer consideration cycles — you cannot read the UTMs from the URL. They are gone.
This recipe solves that problem cleanly. On the landing page visit, it reads any nominated URL query string parameter and writes its value into a browser cookie — either a session cookie that expires when the browser closes, or a persistent cookie that survives for up to 730 days. Those cookie values are then available to every subsequent page view and every GTM event throughout the user's future visits, ready to be attached to GA4 events, read by form scripts, or forwarded in dataLayer pushes. No custom JavaScript. No new Content Security Policy SHA-256 hashes required.
⬇ Download the GTM Container JSON from Google DriveThe Core Problem: UTMs Live in the URL, Not in the Session
A visitor lands on https://example.com/?utm_source=newsletter&utm_medium=email&utm_campaign=spring-offer.
GA4 picks up those parameters automatically for the page_view event. Three days later, in a fresh
browser session, the same visitor returns directly to https://example.com/pricing and
submits a lead form. What UTM values does your CRM receive? Without a persistence layer: none.
GA4's session attribution helps for analytics, but it does not make the original UTMs available to
your form submission handler or marketing automation platform.
Cookies are the browser's built-in persistence mechanism for exactly this purpose. Writing the UTM values to cookies on the first visit and reading them back on the form submission page gives you first-touch attribution data that survives across sessions, browser restarts, and multi-day gaps — without requiring any server-side infrastructure.
<script> block. Any such script requires its
SHA-256 hash to be listed in your Content-Security-Policy header's
script-src directive. Every time the script changes — even whitespace — the hash
changes and must be redeployed. This recipe uses only GTM sandboxed Custom Templates, which execute
inside GTM's own sandbox and require zero additions to your CSP. The cookie-writing and
cookie-rewriting logic runs entirely within the GTM runtime — no new hashes, no security team
review cycles.
What's Inside the Container
The exported container (utm-and-url-query-string-2-cookies-recipe.json) is organised
into three folders of assets, plus the existing Analytics infrastructure and three sandboxed Custom
Templates authored by drewspen.
Folders
| Folder | Contains |
|---|---|
| Analytics | GA4 configuration tag, shared settings variables (including UTM cookie reads), environment/stream routing variables, and client ID and timestamp utilities. |
| Process URL query strings 2 cookies | Two tags (session and user/persistent), plus ten Cookie Variable variables reading the values back from each cookie. Fires at page load to capture incoming UTM parameters. |
| Extend Cookie Life | One tag and two supporting variables. Rewrites the persistent UTM cookies on every page visit to refresh their expiry date, ensuring the 730-day window resets on each visit rather than counting from first write. |
Tags
| Tag | Type | Purpose |
|---|---|---|
Google Analytics Configuration |
Google Tag (googtag) |
Standard GA4 config tag, fires once per load. Shared Event Settings automatically forward UTM cookie values as GA4 event parameters on every hit. |
Write certain URL query strings 2 cookies session |
Custom Template — Write URL query strings 2 cookies | Fires on all pages. Reads utm_source, utm_medium, utm_campaign, utm_content, and utm_term from the URL query string and writes each as a session cookie (expires when the browser closes). Cookie names: utm_source, utm_medium, utm_campaign, utm_content, utm_term. |
Write certain URL query strings 2 cookies user |
Custom Template — Write URL query strings 2 cookies | Fires on all pages (configured with no trigger override — uses the All Pages trigger inherited from the session tag). Reads the same five UTM parameters and writes them as persistent cookies expiring in 730 days (2 years). Cookie names have a _1 suffix: utm_source_1, utm_medium_1, utm_campaign_1, utm_content_1, utm_term_1. |
Extend Cookie Life Tag |
Custom Template — Cookie Rewriter | Fires on DOM Ready, but only if it has not already run in the current page load (guarded by the _arrewr sentinel cookie). Rewrites all ten persistent UTM cookies — both the _1 and _u variants — resetting their expiry to 720 days from now. |
Triggers
| Trigger | Type | Purpose |
|---|---|---|
All Pages (built-in) |
Page View | Fires the two Write query strings tags on every page load so incoming UTM parameters are always captured. |
Extend Cookie Life Trigger |
DOM Ready | Fires the Cookie Rewriter tag, but only when the _arrewr sentinel cookie is absent or empty — ensuring the cookie extension runs exactly once per page, not on every DOM mutation. |
Custom Templates (by drewspen)
| Template | Purpose |
|---|---|
Write URL query strings 2 cookies |
Reads nominated URL query string parameters and writes them to browser cookies, with configurable expiry (session or persistent), cookie name suffix, root domain scoping, and optional URI decoding. Runs entirely within the GTM sandboxed template runtime — no Custom HTML, no CSP implications. |
Cookie Rewriter |
Rewrites a specified list of existing cookies with a refreshed expiry date, allowing persistent cookies to behave as rolling windows that extend on each visit rather than fixed windows from first write. |
If Else If — Advanced Lookup Table (Sublimetrix) |
Powers coalesce and routing logic elsewhere in the container. |
Timestamp (Sublimetrix) |
Reads Unix timestamps from GA4 session and client cookies for shared event parameters. |
Get Root Domain |
Extracts the registrable root domain for cookie scoping, ensuring cookies are accessible across subdomains (e.g. www.example.com and app.example.com). |
Two Tracks: Session Cookies and Persistent Cookies
The recipe deliberately writes each UTM parameter twice — once as a session cookie and once as a persistent cookie. This gives you two distinct attribution windows in a single implementation.
Session Cookies — Current Visit Attribution
Session cookies expire automatically when the browser is closed. They hold the UTM values for the
current visit only. This makes them ideal for "last-touch within session" attribution: if a user
clicks a paid search ad, lands on the site, navigates through three pages, and completes a
transaction, the session cookie carries utm_source=google and
utm_medium=cpc all the way through the checkout flow — even though only the landing
page URL contained those parameters.
| Cookie Name | Type | Content |
|---|---|---|
utm_source | Session | Value of utm_source query parameter on landing |
utm_medium | Session | Value of utm_medium query parameter on landing |
utm_campaign | Session | Value of utm_campaign query parameter on landing |
utm_content | Session | Value of utm_content query parameter on landing |
utm_term | Session | Value of utm_term query parameter on landing |
Persistent Cookies — First-Touch Attribution Across Visits
Persistent cookies survive browser restarts and are retained for up to 730 days (2 years) from
initial write. The _1 suffix in the cookie name is the distinguishing convention:
these cookies hold the values from the first tracked landing, making them the foundation
of first-touch attribution. When the Cookie Rewriter runs on subsequent visits, it refreshes the
expiry without overwriting the values — the original campaign data is preserved.
| Cookie Name | Type | Expiry | Content |
|---|---|---|---|
utm_source_1 | Persistent | 730 days (rolling) | Value of utm_source on first UTM-tagged landing |
utm_medium_1 | Persistent | 730 days (rolling) | Value of utm_medium on first UTM-tagged landing |
utm_campaign_1 | Persistent | 730 days (rolling) | Value of utm_campaign on first UTM-tagged landing |
utm_content_1 | Persistent | 730 days (rolling) | Value of utm_content on first UTM-tagged landing |
utm_term_1 | Persistent | 730 days (rolling) | Value of utm_term on first UTM-tagged landing |
_u variants: The Extend Cookie Life Tag also refreshes cookies
with a _u suffix (utm_campaign_u, utm_source_u, etc.).
These are placeholders for a future-use "last UTM-tagged visit" persistent track — write the
user-level tag a second time with a _u suffix to maintain a rolling record of the
most recent campaign that drove a visit, distinct from the first-touch _1 values.
The extension infrastructure is already in place.
The Rolling Expiry: How the Cookie Rewriter Works
A persistent cookie written once with a 730-day expiry will expire 730 days after it was first written, regardless of how often the user returns. For long sales cycles — a B2B prospect who first visited from a newsletter eleven months ago and finally requests a demo today — a static expiry is often sufficient. But many implementations prefer a rolling expiry: every visit resets the clock, so an active user's attribution never expires.
The Extend Cookie Life Tag achieves this by running the Cookie Rewriter template on
every page visit. It reads each of the ten persistent UTM cookies and rewrites them with the same
value but a new expiry date set 720 days from the current time. The result is a rolling 720-day
window that resets on each visit.
The Sentinel Guard: Firing Exactly Once Per Page
To avoid the Cookie Rewriter firing multiple times per page — once for each DOM Ready event if the
page triggers several — the recipe uses a sentinel cookie named _arrewr. The
Extend Cookie Life Trigger condition checks this cookie's value against the regex
^(undefined|null|0|false|NaN|)$. The tag fires only when the cookie is absent or empty.
The Cookie Rewriter template sets _arrewr on its first run, preventing any subsequent
firing within the same page load.
Cookie Variables: Reading the Values Back
Ten Cookie Variable variables are included in the Process URL query strings 2 cookies folder, one for each cookie. These are GTM's built-in First-Party Cookie variable type and require no custom code.
| Variable Name | Cookie Read | Track |
|---|---|---|
utm_source cookie session | utm_source | Current session |
utm_medium cookie session | utm_medium | Current session |
utm_campaign cookie session | utm_campaign | Current session |
utm_content cookie session | utm_content | Current session |
utm_term cookie session | utm_term | Current session |
utm_source cookie user | utm_source_1 | First touch (persistent) |
utm_medium cookie user | utm_medium_1 | First touch (persistent) |
utm_campaign cookie user | utm_campaign_1 | First touch (persistent) |
utm_content cookie user | utm_content_1 | First touch (persistent) |
utm_term cookie user | utm_term_1 | First touch (persistent) |
How UTM Cookies Flow Into Every GA4 Hit
The cookie variable values are wired directly into the Google Tag Shared Event Settings variable. Because this variable is referenced by the Google Analytics Configuration tag, its parameter table is merged into every GA4 event sent by the container — page views, scroll events, clicks, form submissions, and conversions alike. You receive both the current-session UTMs and the first-touch UTMs on every event, without touching individual tag configurations.
Shared Event Settings — UTM Parameters
| GA4 Parameter | GTM Variable | Attribution Track |
|---|---|---|
e_cur_utm_source | {{utm_source cookie session}} | Source from current session's landing page UTMs |
e_cur_utm_medium | {{utm_medium cookie session}} | Medium from current session's landing page UTMs |
e_cur_utm_campaign | {{utm_campaign cookie session}} | Campaign from current session's landing page UTMs |
e_1st_utm_source | {{utm_source cookie user}} | Source from user's first ever UTM-tagged visit |
e_1st_utm_medium | {{utm_medium cookie user}} | Medium from user's first ever UTM-tagged visit |
e_1st_utm_campaign | {{utm_campaign cookie user}} | Campaign from user's first ever UTM-tagged visit |
utm_content and utm_term, and their cookie variables are available
in the container. The Shared Event Settings above are pre-wired for source,
medium, and campaign — the most commonly used dimensions. Add
e_cur_utm_content, e_cur_utm_term, e_1st_utm_content, and
e_1st_utm_term to the Shared Event Settings table if your reporting requires them.
All Available Query String Parameters
The Write URL query strings 2 cookies template supports a wide range of standard and platform-specific click tracking parameters. The container ships with UTM parameters enabled and all click IDs disabled — a sensible default for most implementations. Enable additional parameters by toggling their checkboxes in the tag configuration UI.
| Parameter | Default | Platform / Purpose |
|---|---|---|
utm_source | ✅ Enabled | Standard UTM — traffic source |
utm_medium | ✅ Enabled | Standard UTM — traffic medium |
utm_campaign | ✅ Enabled | Standard UTM — campaign name |
utm_content | ✅ Enabled | Standard UTM — ad content / variant |
utm_term | ✅ Enabled | Standard UTM — keyword |
gclid | ☐ Disabled | Google Ads — auto-tagged click ID |
gad_source | ☐ Disabled | Google Ads — ad source |
gad_campaignid | ☐ Disabled | Google Ads — campaign ID |
gbraid | ☐ Disabled | Google Ads — iOS app conversion |
wbraid | ☐ Disabled | Google Ads — web-to-app conversion |
gclsrc | ☐ Disabled | Google — DoubleClick import source |
dclid | ☐ Disabled | Google Display & Video 360 click ID |
msclkid | ☐ Disabled | Microsoft Advertising click ID |
fbclid | ☐ Disabled | Meta (Facebook/Instagram) click ID |
igshid | ☐ Disabled | Instagram share ID |
ttclid | ☐ Disabled | TikTok click ID |
ttcid | ☐ Disabled | TikTok campaign ID |
twclid | ☐ Disabled | X (Twitter) click ID |
li_fat_id | ☐ Disabled | LinkedIn first-party ad tracking ID |
ScCID / ScCid | ☐ Disabled | Snapchat click ID |
rdt_cid | ☐ Disabled | Reddit click ID |
srsltid | ☐ Disabled | Google Shopping — surface result listing ID |
utm_id | ☐ Disabled | UTM campaign ID (GA4 extended) |
utm_source_platform | ☐ Disabled | UTM source platform (GA4 extended) |
utm_marketing_tactic | ☐ Disabled | UTM marketing tactic (GA4 extended) |
utm_creative_format | ☐ Disabled | UTM creative format (GA4 extended) |
epic | ☐ Disabled | Custom campaign / partner click ID |
cid | ☐ Disabled | Custom contact or campaign ID |
Architecture: How a Click Becomes a Cookie Becomes a GA4 Parameter
- User clicks a UTM-tagged link — e.g. from a newsletter, paid ad, or social post — and lands on your site with query parameters in the URL.
- GTM fires on All Pages. The Write certain URL query strings 2 cookies session tag runs immediately. For each enabled parameter, it reads the query string value and writes a session cookie scoped to the root domain (e.g.
.example.com). - The user tag also runs. Write certain URL query strings 2 cookies user writes the same values as persistent cookies with the
_1suffix, expiring 730 days from now. If a persistent cookie already exists for a parameter, the existing value is retained — preserving first-touch attribution. - GTM fires DOM Ready. The
Extend Cookie Life Triggerchecks the_arrewrsentinel. If absent, the Extend Cookie Life Tag runs the Cookie Rewriter against all ten persistent UTM cookies, resetting their expiry to 720 days from now. - Cookie Variable variables are evaluated. On every subsequent GTM event, the ten Cookie Variable variables read the current values from the browser cookies.
- Shared Event Settings deliver values to GA4. The six UTM shared event parameters (
e_cur_utm_*ande_1st_utm_*) are included in every GA4 hit automatically via the Google Tag Shared Event Settings variable. - Days or weeks later, the user returns. Their persistent cookies still hold the original first-touch UTM values. On this second visit, the same session cookies are overwritten with the new landing page UTMs (or remain blank if this visit has no UTMs). The persistent cookies are extended again by the Cookie Rewriter.
- The user submits a form. Your form handler can read the UTM cookies directly from
document.cookie, or a separate GTM tag can read the GTM Cookie Variable values and push them into the form's hidden fields or a dataLayer event.
The Real-World Use Case: Lead Attribution Across Days
Consider a B2B software company running LinkedIn campaigns. A prospect clicks an ad on a Monday,
lands on a product page, reads a case study, and leaves without converting. The UTM parameters
from that Monday click are now stored in persistent cookies: utm_source_1=linkedin,
utm_medium_1=paid-social, utm_campaign_1=q2-enterprise-saas.
On Thursday, the prospect types the company URL directly into their browser, reads the pricing page, and submits a contact form. The URL has no UTM parameters. GA4 attributes this session to direct. But your CRM receives the hidden form fields populated from the persistent cookies — and your sales team knows this lead originated from the LinkedIn Q2 enterprise campaign.
This is the gap that UTM-to-cookie bridging fills: GA4 session attribution versus real-world multi-touch, multi-day customer journeys where the conversion rarely happens on the same visit as the first paid impression.
Why Sandboxed Templates Do Not Require New CSP Hashes
GTM's sandboxed Custom Template runtime is granted access to browser APIs through GTM's own
internal permission model — not through Content Security Policy. A template that writes a cookie
uses GTM's setCookie sandbox API, which executes within code already authorised by
the GTM container's existing CSP allowance (the allowance granted to googletagmanager.com
or your server-side GTM origin).
In contrast, a Custom HTML tag writes a literal <script> element into the DOM.
CSP's script-src directive applies to every inline script element. Unless you have
unsafe-inline enabled (which defeats much of CSP's purpose) or a matching SHA-256
hash, the browser will block execution. Every change to that script — adding a comment, reformatting
a line — changes the hash and requires a CSP header update.
With sandboxed templates, your CSP never changes when GTM tag logic changes. The entire cookie management implementation in this recipe is CSP-transparent.
How to Import
- Download the JSON file from the Google Drive link.
- In GTM, go to Admin → Import Container.
- Upload
utm-and-url-query-string-2-cookies-recipe.json. - Choose Merge (not Overwrite) to preserve your existing tags and triggers.
- Update
Measurement Stream ID ProductionandMeasurement Stream ID Developmentconstant variables to your actual GA4 Measurement IDs. - Review the enabled query string parameters in both Write tags and toggle on any additional parameters (click IDs, custom fields) that your implementation requires.
- Verify that
{{Root Domain}}resolves correctly for your site — this controls the cookie's domain scope. Check its value in GTM Preview mode on your site. - In GA4, register
e_cur_utm_source,e_cur_utm_medium,e_cur_utm_campaign,e_1st_utm_source,e_1st_utm_medium, ande_1st_utm_campaignas custom event-scoped dimensions under Admin → Custom definitions → Custom dimensions. - Open GTM Preview mode on a URL with UTM parameters (e.g.
?utm_source=test&utm_medium=test&utm_campaign=test) and confirm the cookie variables populate correctly in the Tag Assistant panel. - Navigate to a second page with no UTM parameters and confirm the session and persistent cookie values persist across the navigation.
What You Can Do With It
- Attach first-touch UTM values to CRM lead records by reading persistent cookies from hidden form fields — connect campaign spend to closed revenue without server-side infrastructure
- Compare
e_cur_utm_campaign(session UTM) againste_1st_utm_campaign(first-touch UTM) in GA4 Explorations to understand how many conversions involve multi-campaign journeys - Persist platform click IDs (
gclid,fbclid,msclkid) by enabling the relevant parameters — forward them to your CRM for enhanced conversions and offline conversion imports - Build BigQuery attribution models that join on
e_1st_utm_sourceto compare first-touch against data-driven attribution without rebuilding your tracking implementation - Extend the cookie name suffix convention (
_1for first touch,_ufor last touch) to create a full first/last attribution duality in a single container - Use session UTM cookies to personalise on-site content via GTM variables — show a returning email subscriber a different headline without any server-side personalisation
The full source — including the container JSON, variable inventory, and the drewspen sandboxed Custom Templates — is available on GitHub. If you enable additional query string parameters or extend the cookie naming convention, open a pull request or issue.
⬇ Download GTM Container JSON
Comments
Post a Comment