Last active
April 14, 2026 15:46
-
-
Save ceaksan/f7df7937cbb8455c57237d39d60df180 to your computer and use it in GitHub Desktop.
GTM Custom HTML: First-touch source capture — Reads UTM/gclid/fbclid from landing URL, writes to dnm_src and dnm_cid via storageHelper (cookie + localStorage dual-write). Overwrites only on new click ID. Requires storageHelper companion gist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * GTM Custom HTML Tag — First-touch source capture | |
| * File: gtm-source-capture.js | |
| * | |
| * What it does: | |
| * Reads utm_*, gclid, fbclid parameters from the landing URL | |
| * and writes them to two storage keys via storageHelper: | |
| * "dnm_src" and "dnm_cid". | |
| * | |
| * Requires: | |
| * storageHelper (see companion gist) must be loaded BEFORE this tag. | |
| * Use GTM Tag Sequencing to ensure correct order. | |
| * | |
| * Overwrite logic: | |
| * - If no stored data exists → always write | |
| * - If data exists → only overwrite when a new click ID | |
| * (gclid/fbclid) is present in the URL | |
| * - Organic/direct return visits do NOT erase previous paid source | |
| * | |
| * Storage TTL: 30 days (cookie) + localStorage fallback (ITP-resilient) | |
| * Cookie domain: auto-detected by storageHelper (covers subdomains) | |
| * | |
| * GDPR / privacy note: | |
| * This script does NOT perform consent checks. | |
| * In GTM, set the trigger condition to: analytics_storage = granted. | |
| */ | |
| (function () { | |
| /* --- Constants ------------------------------------------------- */ | |
| var DNM_TTL = 2592000; // 30 days in seconds | |
| var KEY_SOURCE = "dnm_src"; // source|medium|campaign|content|term | |
| var KEY_CID = "dnm_cid"; // click ID type + value | |
| /* --- Helper: read URL parameter -------------------------------- */ | |
| function getParam(name) { | |
| var match = window.location.search.match( | |
| new RegExp("[?&]" + name + "=([^&]*)"), | |
| ); | |
| return match ? decodeURIComponent(match[1].replace(/\+/g, " ")) : ""; | |
| } | |
| /* --- 1. Collect URL parameters --------------------------------- */ | |
| var source = getParam("utm_source"); | |
| var medium = getParam("utm_medium"); | |
| var campaign = getParam("utm_campaign"); | |
| var content = getParam("utm_content"); | |
| var term = getParam("utm_term"); | |
| var gclid = getParam("gclid"); | |
| var fbclid = getParam("fbclid"); | |
| // Determine click ID type | |
| var clickId = gclid ? gclid : fbclid ? fbclid : ""; | |
| var clickType = gclid ? "gclid" : fbclid ? "fbclid" : ""; | |
| // Derive source from click ID when UTMs are missing (auto-tagging) | |
| if (!source && gclid) { | |
| source = "google"; | |
| medium = "cpc"; | |
| } | |
| if (!source && fbclid) { | |
| source = "facebook"; | |
| medium = "paid_social"; | |
| } | |
| /* --- 2. Overwrite decision ------------------------------------- */ | |
| var existing = storageHelper.get(KEY_SOURCE); | |
| // Write conditions: | |
| // a) No stored data yet | |
| // b) A new click ID is present (paid click overrides previous) | |
| var shouldWrite = !existing || clickId !== ""; | |
| if (!shouldWrite) { | |
| return; | |
| } // preserve existing, exit | |
| if (!source) { | |
| return; | |
| } // nothing to save, exit | |
| /* --- 3. Build values ------------------------------------------- */ | |
| // dnm_src format: "source|medium|campaign|content|term" | |
| var srcValue = [ | |
| source || "", | |
| medium || "", | |
| campaign || "", | |
| content || "", | |
| term || "", | |
| ].join("|"); | |
| // dnm_cid format: "type:value" e.g. "gclid:Cj0KCQjw..." | |
| var cidValue = clickType && clickId ? clickType + ":" + clickId : ""; | |
| /* --- 4. Write via storageHelper (cookie + localStorage) -------- */ | |
| var opts = { maxAge: DNM_TTL }; | |
| storageHelper.set(KEY_SOURCE, srcValue, opts); | |
| if (cidValue) { | |
| storageHelper.set(KEY_CID, cidValue, opts); | |
| } | |
| /* --- 5. Push to dataLayer (for use as GTM variables) ----------- */ | |
| window.dataLayer = window.dataLayer || []; | |
| window.dataLayer.push({ | |
| event: "dnm_source_captured", | |
| dnm_first_source: source || "", | |
| dnm_first_medium: medium || "", | |
| dnm_first_campaign: campaign || "", | |
| dnm_first_click_id: cidValue || "", | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment