GTM Starter Kit
GTM Starter Kit
A ready-to-import Google Tag Manager container that stands up a baseline of form-submit conversion tracking for GA4 and Meta, built on top of the GTM Essentials variables. Import it into a fresh (or existing) web container to get the tags, trigger, and variables below.
Quick import
- GTM → Admin → Import Container.
- Choose
gtm-starter-kit.json, select your workspace. - Pick Merge and Rename conflicting so nothing existing is overwritten.
- Preview, then Confirm.
Placeholder account/container IDs (0) are remapped into whatever container you
import into. Everything lands in two folders: GTM Starter Kit (the tags,
trigger, and new variables) and GTM Essentials (the shared variables).
Before it works, do these three things (see Setup):
set your GA4 Measurement ID, make sure the Meta Pixel base is loaded, and make
sure something pushes form_submit to the dataLayer.
What it creates
Tags — folder GTM Starter Kit
| Tag | Type | Fires on | Notes |
|---|---|---|---|
| Config - GA4 | Google tag | Initialization - All Pages | Tag ID = {{GA4 - Measurement ID}}. |
| GA4 - Event - Form Submit | GA4 Event | form_submit | Event name ga4e_form_submit, Measurement ID {{GA4 - Measurement ID}}, Once per event. Parameters below. |
| Meta - Event - Lead - Form Submit | Custom HTML | form_submit | Fires fbq('track', 'Lead', …) with page path / traffic source / ad placement, plus eventID: {{cJS - Custom Event ID}} for browser/server dedupe. |
| Conversion Linker | Conversion Linker | All Pages | Improves Google Ads click-ID cookie durability. |
GA4 - Event - Form Submit → event parameters:
| Parameter | Value |
|---|---|
cjs_traffic_source | {{cJS - Traffic Source}} |
cjs_ad_placement | {{cJS - Ad Placement}} |
Trigger — folder GTM Starter Kit
- form_submit — Custom Event, event name
form_submit(fires on everyform_submitdataLayer event). Drives both the GA4 event and Meta tags.
Variables — folder GTM Starter Kit
- GA4 - Measurement ID — Constant,
G-0000000000. Replace with your real Measurement ID — every GA4 tag reads from this one place.
User-Provided Data variable — add this one by hand. GTM's User-Provided Data variable has no stable container-export type, so it isn't in the JSON. Add it after import: Variables → New → Variable Configuration → User-Provided Data, set it to Automatic, and name it
User Provided Data. It's a two-click add and pairs with GA4 / Google Ads enhanced conversions.
Built-in variables enabled
Click Classes / Element / ID / Target / Text / URL, Event, Form Classes / Element / ID / Target / Text / URL, HTML ID, Page Hostname / Path / URL, Referrer.
Included from GTM Essentials
These variables come from the GTM Essentials toolkit
entry and are bundled here (folder GTM Essentials) so the kit is self-contained.
Full details and source files are in
gtm-essentials/README.md.
Maintenance — keep this in sync with GTM Essentials. The variables below are copies baked into
gtm-starter-kit.json. Whenever GTM Essentials gains or changes a variable, refresh this export too so the copies don't drift. (A.jsonfile can't carry an inline comment, so this reminder lives here.)
- First-party cookie variables (
1PC - …):_fbc,_fbp,fbclid,_gcl_aw,_uetmsclkid,_ttp,_twclid,li_fat_id. cJS - Traffic Source— last-touch traffic source (source).cJS - Ad Placement—utm_placement, persisted per session (source).DLV - gtm.uniqueEventId— GTM's built-in page-local event counter; cache key for the variable below.cJS - Custom Event ID— globally unique event ID for Meta Pixel / CAPI deduplication, cached per{{DLV - gtm.uniqueEventId}}(source).cJS - Page Title,cJS - Post Title,cJS - Post ID,cJS - Form ID— page/post/form context (sources).
Setup after import
- GA4 - Measurement ID → set the Constant to your
G-XXXXXXXXXX. - Meta Pixel base must load before Meta - Event - Lead - Form Submit
(the tag calls
fbq(...), sofbqhas to exist). Add your Pixel base tag, ideally gated on consent. form_submitevent must be pushed to the dataLayer. The toolkit's Elementor Listener pushes exactly this event; use it (or your platform's equivalent listener).
Things to verify on import
- GA4 event Measurement ID — confirm the tag's Measurement ID field resolves
to
{{GA4 - Measurement ID}}(or point it at the Config - GA4 Google tag). - User-Provided Data — not in the JSON (see above); add it via the UI.
- Trigger mapping — Config - GA4 should fire on Initialization - All Pages and Conversion Linker on All Pages; re-select if either didn't map.
Suggestions
- Firing option: every tag ships set to Once per event (Advanced Settings → Tag firing options), so each trigger event fires the tag at most once — including repeat form submits on the same page load.
- Consent: for Consent Mode setups, fire Conversion Linker on Consent Initialization - All Pages, and gate the Meta tag on marketing consent.
The code
{
"exportFormatVersion": 2,
"exportTime": "2026-08-04 00:00:00",
"containerVersion": {
"path": "accounts/0/containers/0/versions/0",
"accountId": "0",
"containerId": "0",
"containerVersionId": "0",
"name": "GTM Starter Kit",
"container": {
"path": "accounts/0/containers/0",
"accountId": "0",
"containerId": "0",
"name": "GTM Starter Kit",
"publicId": "GTM-XXXXXXX",
"usageContext": [
"WEB"
],
"fingerprint": "0",
"tagManagerUrl": ""
},
"tag": [
{
"accountId": "0",
"containerId": "0",
"tagId": "1",
"name": "Config - GA4",
"type": "googtag",
"parameter": [
{
"type": "TEMPLATE",
"key": "tagId",
"value": "{{GA4 - Measurement ID}}"
}
],
"firingTriggerId": [
"2147479573"
],
"tagFiringOption": "ONCE_PER_EVENT",
"fingerprint": "0",
"parentFolderId": "2"
},
{
"accountId": "0",
"containerId": "0",
"tagId": "2",
"name": "GA4 - Event - Form Submit",
"type": "gaawe",
"parameter": [
{
"type": "TEMPLATE",
"key": "eventName",
"value": "ga4e_form_submit"
},
{
"type": "BOOLEAN",
"key": "sendEcommerceData",
"value": "false"
},
{
"type": "LIST",
"key": "eventParameters",
"list": [
{
"type": "MAP",
"map": [
{
"type": "TEMPLATE",
"key": "name",
"value": "cjs_traffic_source"
},
{
"type": "TEMPLATE",
"key": "value",
"value": "{{cJS - Traffic Source}}"
}
]
},
{
"type": "MAP",
"map": [
{
"type": "TEMPLATE",
"key": "name",
"value": "cjs_ad_placement"
},
{
"type": "TEMPLATE",
"key": "value",
"value": "{{cJS - Ad Placement}}"
}
]
}
]
},
{
"type": "TEMPLATE",
"key": "measurementIdOverride",
"value": "{{GA4 - Measurement ID}}"
}
],
"firingTriggerId": [
"1"
],
"tagFiringOption": "ONCE_PER_EVENT",
"fingerprint": "0",
"parentFolderId": "2"
},
{
"accountId": "0",
"containerId": "0",
"tagId": "3",
"name": "Meta - Event - Lead - Form Submit",
"type": "html",
"parameter": [
{
"type": "TEMPLATE",
"key": "html",
"value": "<!--\n Marketing Toolkit — Meta Pixel — Lead Event\n Version: v1.3.0\n Last updated: 2026-08-13\n https://github.com/whboggs/marketing-toolkit\n-->\n<script>\nfbq('track', 'Lead', {\n lead_type: 'Form Submit', // Define Lead Type\n // form_name: // (Optional) Translate form ID to Form Name via LUT or Variable\n content_name: '{{Page Path}}', // Finds page path\n traffic_source: '{{cJS - Traffic Source}}', // Finds Last Touch traffic source\n ad_placement: '{{cJS - Ad Placement}}' // Finds ad placement\n}, {\n eventID: '{{cJS - Custom Event ID}}' // Meta's browser/server dedupe key\n});\n</script>"
},
{
"type": "BOOLEAN",
"key": "supportDocumentWrite",
"value": "false"
}
],
"firingTriggerId": [
"1"
],
"tagFiringOption": "ONCE_PER_EVENT",
"fingerprint": "0",
"parentFolderId": "2"
},
{
"accountId": "0",
"containerId": "0",
"tagId": "4",
"name": "Conversion Linker",
"type": "gclidw",
"parameter": [
{
"type": "BOOLEAN",
"key": "enableCrossDomain",
"value": "false"
}
],
"firingTriggerId": [
"2147479553"
],
"tagFiringOption": "ONCE_PER_EVENT",
"fingerprint": "0",
"parentFolderId": "2"
}
],
"trigger": [
{
"accountId": "0",
"containerId": "0",
"triggerId": "1",
"name": "form_submit",
"type": "CUSTOM_EVENT",
"customEventFilter": [
{
"type": "EQUALS",
"parameter": [
{
"type": "TEMPLATE",
"key": "arg0",
"value": "{{_event}}"
},
{
"type": "TEMPLATE",
"key": "arg1",
"value": "form_submit"
}
]
}
],
"fingerprint": "0",
"parentFolderId": "2"
}
],
"builtInVariable": [
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_CLASSES",
"name": "Click Classes"
},
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_ELEMENT",
"name": "Click Element"
},
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_ID",
"name": "Click ID"
},
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_TARGET",
"name": "Click Target"
},
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_TEXT",
"name": "Click Text"
},
{
"accountId": "0",
"containerId": "0",
"type": "CLICK_URL",
"name": "Click URL"
},
{
"accountId": "0",
"containerId": "0",
"type": "EVENT",
"name": "Event"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_CLASSES",
"name": "Form Classes"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_ELEMENT",
"name": "Form Element"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_ID",
"name": "Form ID"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_TARGET",
"name": "Form Target"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_TEXT",
"name": "Form Text"
},
{
"accountId": "0",
"containerId": "0",
"type": "FORM_URL",
"name": "Form URL"
},
{
"accountId": "0",
"containerId": "0",
"type": "HTML_ID",
"name": "HTML ID"
},
{
"accountId": "0",
"containerId": "0",
"type": "PAGE_HOSTNAME",
"name": "Page Hostname"
},
{
"accountId": "0",
"containerId": "0",
"type": "PAGE_PATH",
"name": "Page Path"
},
{
"accountId": "0",
"containerId": "0",
"type": "PAGE_URL",
"name": "Page URL"
},
{
"accountId": "0",
"containerId": "0",
"type": "REFERRER",
"name": "Referrer"
}
],
"folder": [
{
"accountId": "0",
"containerId": "0",
"folderId": "1",
"name": "GTM Essentials",
"fingerprint": "0"
},
{
"accountId": "0",
"containerId": "0",
"folderId": "2",
"name": "GTM Starter Kit",
"fingerprint": "0"
}
],
"variable": [
{
"accountId": "0",
"containerId": "0",
"variableId": "1",
"name": "1PC - _fbc",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_fbc"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "2",
"name": "1PC - _fbp",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_fbp"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "3",
"name": "1PC - fbclid",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "fbclid"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "4",
"name": "1PC - _gcl_aw",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_gcl_aw"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "5",
"name": "1PC - _uetmsclkid",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_uetmsclkid"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "6",
"name": "1PC - _ttp",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_ttp"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "7",
"name": "1PC - _twclid",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "_twclid"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "8",
"name": "1PC - li_fat_id",
"type": "k",
"parameter": [
{
"type": "TEMPLATE",
"key": "name",
"value": "li_fat_id"
},
{
"type": "BOOLEAN",
"key": "decodeCookie",
"value": "true"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "17",
"name": "cJS - Traffic Source",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n // cJS - Traffic Source\n //\n // Last-touch traffic source for lead gen and ecommerce conversion tags, returned as one\n // flat snake_case string (google_ads, facebook_ads, google_organic, newsletter_email,\n // referral, direct, ...).\n //\n // Classification mirrors the MTK Attribution engine (resolveTouch): the same click IDs,\n // the same utm_medium values, the same referrer domain lists, and the same fresh-cookie\n // rescue — collapsed to a single string instead of channel + source.\n //\n // An ENTRY is resolved once, from the pageview that brought the visitor in, and carried\n // through the tab in sessionStorage so popup forms and multi-step checkouts on internal\n // pages still report the source that started the visit. A later pageview only replaces\n // the stored entry when it carries a fresh campaign signal in the URL (a click ID or a\n // UTM) or the tab has been idle for longer than the session window — internal navigation\n // never changes attribution, and neither does a mid-funnel bounce through a payment\n // gateway or an OAuth provider.\n\n var STORE_KEY = 'cjs_traffic_source'; // sessionStorage key: {\"v\": \"<source>\", \"t\": <last activity ms>}\n var SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes, GA-style — idle longer than this = new entry\n var PAID_SOCIAL_MEDIUM = 'paid_social'; // the utm_medium ad URLs must carry to count as paid social\n\n // Referrer domain lists — same fragments as the MTK engine defaults. A trailing dot means\n // \"this label anywhere in the host\" (google.com, news.google.de); an inner dot means \"this\n // exact domain or a subdomain of it\" (x.com, www.x.com — but never wix.com).\n var SEARCH_DOMAINS = ['google.', 'bing.', 'yahoo.', 'duckduckgo.', 'ecosia.'];\n var SOCIAL_DOMAINS = ['facebook.', 'fb.', 'instagram.', 'linkedin.', 'twitter.', 'x.com', 'youtube.', 'reddit.', 'pinterest.', 'tiktok.', 't.co'];\n\n // utm_source aliases -> the platform name this variable reports. Covers Meta's\n // {{site_source_name}} values (fb / ig / msg / an) recommended in the UTM docs, so a paid\n // Facebook click tagged utm_source=fb returns facebook_ads, not fb_ads.\n var SOURCE_ALIASES = {\n fb: 'facebook', ig: 'instagram', msg: 'messenger', an: 'audience_network',\n x: 'twitter', 'x.com': 'twitter',\n googleads: 'google', adwords: 'google',\n bingads: 'microsoft', bing: 'microsoft'\n };\n\n // ---------------------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------------------\n\n var params = new URLSearchParams(window.location.search);\n function param(key) { return params.get(key) || ''; }\n function lower(s) { return s ? String(s).toLowerCase() : ''; }\n\n // Turn any typed value into a safe snake_case token (\"Facebook Ads\" -> \"facebook_ads\").\n function slug(s) {\n return lower(s).replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');\n }\n\n // Alias lookup, then slug — so utm_source=IG and utm_source=Instagram both give \"instagram\".\n function sourceName(raw) {\n var key = lower(raw);\n if (!key) return '';\n return SOURCE_ALIASES.hasOwnProperty(key) ? SOURCE_ALIASES[key] : slug(key);\n }\n\n // sessionStorage access that degrades to \"no persistence\" in private browsing / blocked storage.\n function readStore() {\n try { var raw = sessionStorage.getItem(STORE_KEY); return raw ? JSON.parse(raw) : null; } catch (e) { return null; }\n }\n function writeStore(value) {\n try { sessionStorage.setItem(STORE_KEY, JSON.stringify({ v: value, t: Date.now() })); } catch (e) {}\n }\n\n // Referrer HOST only — scheme, userinfo, port, and path stripped — so a path or query string\n // containing \"x.com\" or \"google.\" can never be mistaken for the referring domain.\n var referrer = lower(document.referrer);\n var refHost = referrer\n .replace(/^[a-z][a-z0-9+.-]*:\\/\\//, '')\n .split('/')[0]\n .split('@').pop()\n .split(':')[0];\n var selfHost = lower(window.location.hostname);\n\n // A referrer from the site's own hostname is internal navigation, not attribution.\n var hasReferrer = referrer !== '' && refHost !== selfHost;\n\n // Boundary-aware domain match against refHost (see SEARCH_DOMAINS comment for the styles).\n function hostMatches(frag) {\n if (!frag) return false;\n if (frag.charAt(frag.length - 1) === '.') {\n // \"google.\" — label prefix anywhere in the host\n return refHost.indexOf(frag) === 0 || refHost.indexOf('.' + frag) > -1;\n }\n if (frag.indexOf('.') > -1) {\n // \"x.com\" — exact registrable domain or a subdomain of it\n if (refHost === frag) return true;\n var tail = '.' + frag;\n return refHost.length > tail.length && refHost.indexOf(tail) === refHost.length - tail.length;\n }\n return refHost.indexOf(frag) > -1; // plain substring for loose custom entries\n }\n function matchesList(list) {\n for (var i = 0; i < list.length; i++) { if (hostMatches(list[i])) return true; }\n return false;\n }\n\n // The platform name behind a referrer, used for the \"<name>_organic\" buckets. Anything not\n // listed reports its registrable-ish host label (e.g. \"ecosia\" for ecosia.org).\n function referrerName() {\n if (hostMatches('google.')) return 'google';\n if (hostMatches('bing.')) return 'bing';\n if (hostMatches('yahoo.')) return 'yahoo';\n if (hostMatches('duckduckgo.')) return 'duckduckgo';\n if (hostMatches('facebook.') || hostMatches('fb.')) return 'facebook';\n if (hostMatches('instagram.')) return 'instagram';\n if (hostMatches('linkedin.')) return 'linkedin';\n if (hostMatches('twitter.') || hostMatches('x.com') || hostMatches('t.co')) return 'twitter';\n if (hostMatches('youtube.')) return 'youtube';\n if (hostMatches('reddit.')) return 'reddit';\n if (hostMatches('pinterest.')) return 'pinterest';\n if (hostMatches('tiktok.')) return 'tiktok';\n var labels = refHost.replace(/^www\\./, '').split('.');\n return slug(labels.length > 1 ? labels[labels.length - 2] : labels[0]);\n }\n\n // Cookie reader — exact name match, so \"_gcl_aw\" can't be satisfied by \"x_gcl_aw\".\n function readCookie(name) {\n var parts = document.cookie ? document.cookie.split('; ') : [];\n for (var i = 0; i < parts.length; i++) {\n var pair = parts[i].split('=');\n if (pair[0] === name) {\n try { return decodeURIComponent(pair.slice(1).join('=')); } catch (e) { return ''; }\n }\n }\n return '';\n }\n\n // Platform cookies embed the click ID AND its creation time. Only a FRESH cookie — created\n // within the session window — may classify an entry. A stale one just means \"returning\n // visitor\" (these cookies live ~90 days); using it would erase Direct and double-credit ads\n // for months. Returns the click ID, or null.\n function freshClickCookie(name, tsIndex, tsUnit) {\n var raw = readCookie(name);\n if (!raw) return null;\n var parts = raw.split('.');\n var ts = parseInt(parts[tsIndex], 10);\n if (!ts) return null;\n if (tsUnit === 's') ts = ts * 1000;\n var age = Date.now() - ts;\n if (age < 0 || age > SESSION_TIMEOUT) return null;\n return parts.slice(tsIndex + 1).join('.') || null;\n }\n function freshGclid() { return freshClickCookie('_gcl_aw', 1, 's'); } // \"_gcl_aw\" = GCL.{seconds}.{gclid}\n function freshFbclid() { return freshClickCookie('_fbc', 2, 'ms'); } // \"_fbc\" = fb.1.{ms}.{fbclid}\n\n // Does THIS pageview carry a campaign signal — any UTM or any ad-platform click ID? Read from\n // the URL only: cookies outlive the click by weeks and must never look like a fresh arrival.\n function hasCampaignSignal() {\n if (param('utm_source') || param('utm_medium') || param('utm_campaign')) return true;\n return !!(param('gclid') || param('gbraid') || param('wbraid') ||\n param('msclkid') || param('fbclid') || param('ttclid'));\n }\n\n // ---------------------------------------------------------------------------------------\n // Resolver — the MTK engine's rule order, flattened to one string. First match wins.\n // ---------------------------------------------------------------------------------------\n function resolve() {\n var utmSourceRaw = param('utm_source');\n var utmSource = lower(utmSourceRaw);\n var utmMedium = lower(param('utm_medium'));\n\n var gclid = param('gclid');\n var gbraid = param('gbraid'); // iOS web-to-app Google Ads click ID\n var wbraid = param('wbraid'); // iOS app-to-web Google Ads click ID\n var msclkid = param('msclkid');\n var fbclid = param('fbclid');\n var ttclid = param('ttclid');\n\n // 1. Paid Search — any Google / Microsoft click ID, or utm_medium=cpc / paid_search\n // (manual tagging with auto-tagging off, or a click ID stripped by a redirect).\n if (gclid || gbraid || wbraid || msclkid || utmMedium === 'cpc' || utmMedium === 'paid_search') {\n if (msclkid || utmSource === 'bing' || utmSource === 'bingads' || utmSource === 'microsoft') return 'microsoft_ads';\n if (!utmSource || utmSource === 'google' || utmSource === 'googleads' || utmSource === 'adwords') return 'google_ads';\n return sourceName(utmSourceRaw) + '_ads'; // some other paid-search network, tagged by hand\n }\n\n // 2. Paid Social — REQUIRES the utm_medium convention. Meta appends fbclid to organic posts\n // too, so fbclid / _fbc alone can never prove a paid click.\n if (utmMedium === PAID_SOCIAL_MEDIUM) {\n return (sourceName(utmSourceRaw) || 'facebook') + '_ads';\n }\n\n // 3. TikTok Ads — ttclid is paid-only (organic TikTok links don't carry it).\n if (ttclid) return 'tiktok_ads';\n\n // 4. Email — \"<source>_email\" (newsletter_email, klaviyo_email), or plain \"email\" when untagged.\n if (utmMedium === 'email') {\n return utmSource ? sourceName(utmSourceRaw) + '_email' : 'email';\n }\n\n // 5. Any other tagged campaign — \"<source>_<medium>\" (partner_referral, podcast_sponsorship),\n // or just \"<source>\" when there is no medium.\n if (utmSource) {\n return utmMedium ? sourceName(utmSourceRaw) + '_' + slug(utmMedium) : sourceName(utmSourceRaw);\n }\n\n // 6. Untagged arrival with an external referrer.\n if (hasReferrer) {\n if (matchesList(SEARCH_DOMAINS)) {\n // Same-platform upgrade: an ad click FROM google.com whose gclid was lost in transit\n // still set a fresh _gcl_aw — that's Google Ads, not organic.\n if (hostMatches('google.') && freshGclid()) return 'google_ads';\n return referrerName() + '_organic';\n }\n if (matchesList(SOCIAL_DOMAINS) || fbclid) {\n // Same-platform upgrade — but ONLY when the URL carried no fbclid. The Pixel writes _fbc\n // from whichever fbclid it sees, so a fresh _fbc next to an fbclid is just that (possibly\n // organic) click echoed back. With no fbclid in the URL, a fresh _fbc is real evidence of\n // a paid click whose parameter went missing.\n if (!fbclid && (hostMatches('facebook.') || hostMatches('fb.') || hostMatches('instagram.')) && freshFbclid()) {\n return 'facebook_ads';\n }\n // fbclid from a referrer outside the social list (e.g. a Messenger in-app browser) is still Meta.\n return (matchesList(SOCIAL_DOMAINS) ? referrerName() : 'facebook') + '_organic';\n }\n return 'referral'; // some other site we don't bucket\n }\n\n // 7. fbclid with no referrer at all (some in-app browsers strip it) — still an organic Meta click.\n if (fbclid) return 'facebook_organic';\n\n // 8. Cookie rescue — last resort before Direct. A FRESH platform cookie means an ad click just\n // happened but its URL signal never reached us (consent redirect, URL rewrite, tag first\n // firing mid-session). Stale cookies never classify.\n if (freshGclid()) return 'google_ads';\n if (freshFbclid()) return 'facebook_ads';\n\n // 9. Nothing identifying — typed URL, bookmark, app, dark social.\n return 'direct';\n }\n\n // ---------------------------------------------------------------------------------------\n // Entry handling — reuse the stored entry through internal navigation; re-resolve on a fresh\n // campaign signal, after the idle window, or when nothing is stored yet.\n // ---------------------------------------------------------------------------------------\n var stored = readStore();\n var expired = !stored || !stored.t || (Date.now() - stored.t) > SESSION_TIMEOUT;\n\n if (stored && stored.v && !expired && !hasCampaignSignal()) {\n writeStore(stored.v); // touch last-activity, keep the value\n return stored.v;\n }\n\n var value = resolve();\n writeStore(value);\n return value;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "18",
"name": "cJS - Ad Placement",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n // Captures utm_placement from URL on first pageview, persists via sessionStorage.\n // Handles both Meta placements (e.g. \"Audience_Network_Native\", \"Facebook_Mobile_Feed\")\n // and Google Ads placements (e.g. \"g_1t1\" for top-of-page search, \"gmb_\" for Business Profile,\n // \"d_\" for Display Network). The variable doesn't care which platform set the value —\n // utm_source already tells you that. This just captures whatever placement was passed.\n \n var STORAGE_KEY = 'ad_placement';\n \n try {\n // First, check if utm_placement is in the current URL.\n // If yes, this is the freshest signal — overwrite anything stored.\n var urlParams = new URLSearchParams(window.location.search);\n var placementFromUrl = urlParams.get('utm_placement');\n \n if (placementFromUrl) {\n try { sessionStorage.setItem(STORAGE_KEY, placementFromUrl); } catch(e) {}\n return placementFromUrl;\n }\n \n // No utm_placement in current URL — fall back to whatever we stored\n // earlier in this session (e.g. from the original landing page).\n try {\n var stored = sessionStorage.getItem(STORAGE_KEY);\n if (stored) return stored;\n } catch(e) {}\n \n // No URL value, no stored value — return undefined.\n // GA4 will simply omit the parameter rather than send an empty string.\n return undefined;\n \n } catch (e) {\n // URLSearchParams unavailable (very old browsers) or other failure\n return undefined;\n }\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "19",
"name": "cJS - Page Title",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n return document.title;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "20",
"name": "cJS - Post Title",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n var el = document.querySelector('.entry-title, h1.entry-title, h1');\n if (el && el.textContent) return el.textContent.trim();\n var og = document.querySelector('meta[property=\"og:title\"]');\n return og ? og.getAttribute('content') : undefined;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "21",
"name": "cJS - Post ID",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n var m = (document.body.className || '').match(/(?:postid|page-id)-(\\d+)/);\n return m ? m[1] : undefined;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "22",
"name": "cJS - Form ID",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n var f = document.querySelector('form[id]');\n return f ? f.id : undefined;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "23",
"name": "GA4 - Measurement ID",
"type": "c",
"parameter": [
{
"type": "TEMPLATE",
"key": "value",
"value": "G-0000000000"
}
],
"fingerprint": "0",
"parentFolderId": "2"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "24",
"name": "DLV - gtm.uniqueEventId",
"type": "v",
"parameter": [
{
"type": "INTEGER",
"key": "dataLayerVersion",
"value": "2"
},
{
"type": "BOOLEAN",
"key": "setDefaultValue",
"value": "false"
},
{
"type": "TEMPLATE",
"key": "name",
"value": "gtm.uniqueEventId"
}
],
"fingerprint": "0",
"parentFolderId": "1"
},
{
"accountId": "0",
"containerId": "0",
"variableId": "25",
"name": "cJS - Custom Event ID",
"type": "jsm",
"parameter": [
{
"type": "TEMPLATE",
"key": "javascript",
"value": "function() {\n var id = {{DLV - gtm.uniqueEventId}}; // Page-local counter — the cache key\n\n if (window.customEventIdKey !== id) {\n window.customEventIdKey = id;\n // Globally unique value — this is what actually goes to Meta\n window.customEventId = (new Date()).getTime() + '.' +\n Math.random().toString(36).substring(2, 10);\n }\n\n return window.customEventId;\n}"
}
],
"fingerprint": "0",
"parentFolderId": "1"
}
],
"fingerprint": "0"
}
}