Skip to content

Instantly share code, notes, and snippets.

@ceaksan
Last active April 14, 2026 15:46
Show Gist options
  • Select an option

  • Save ceaksan/f7df7937cbb8455c57237d39d60df180 to your computer and use it in GitHub Desktop.

Select an option

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.
/**
* 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