tag
*
* USAGE IN THEME.LIQUID:
*
*
*
* @version 1.0.0
* @date 2026-01-14
* @project Rockwell Growth Marketing - Attribution Fix
*/
(function() {
'use strict';
// Configuration
var UTM_STORAGE_KEY = 'rockwell_utm_data';
var UTM_EXPIRY_HOURS = 24; // How long to keep UTM data in localStorage
// List of parameters to capture
var CAPTURE_PARAMS = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_content',
'utm_term',
'fbclid', // Meta/Facebook click ID
'gclid', // Google click ID
'ttclid', // TikTok click ID
'msclkid' // Microsoft/Bing click ID
];
/**
* Parse URL parameters
* @returns {Object} Key-value pairs of captured parameters
*/
function getUrlParams() {
var params = {};
try {
var searchParams = new URLSearchParams(window.location.search);
CAPTURE_PARAMS.forEach(function(key) {
var value = searchParams.get(key);
if (value) {
params[key] = value;
}
});
} catch (e) {
// Fallback for older browsers
var queryString = window.location.search.substring(1);
if (queryString) {
queryString.split('&').forEach(function(pair) {
var parts = pair.split('=');
var key = decodeURIComponent(parts[0]);
var value = parts[1] ? decodeURIComponent(parts[1]) : '';
if (CAPTURE_PARAMS.indexOf(key) !== -1 && value) {
params[key] = value;
}
});
}
}
return params;
}
/**
* Store UTM data in sessionStorage (per-tab)
* @param {Object} params - UTM parameters to store
*/
function storeInSession(params) {
try {
Object.keys(params).forEach(function(key) {
sessionStorage.setItem(key, params[key]);
});
// Also store landing page for reference
sessionStorage.setItem('utm_landing_page', window.location.pathname);
sessionStorage.setItem('utm_captured_at', new Date().toISOString());
} catch (e) {
console.warn('[UTM Capture] sessionStorage not available:', e);
}
}
/**
* Store UTM data in localStorage (persists across tabs/sessions)
* @param {Object} params - UTM parameters to store
*/
function storeInLocal(params) {
try {
var data = {
params: params,
landing_page: window.location.pathname,
captured_at: new Date().getTime(),
expires_at: new Date().getTime() + (UTM_EXPIRY_HOURS * 60 * 60 * 1000)
};
localStorage.setItem(UTM_STORAGE_KEY, JSON.stringify(data));
} catch (e) {
console.warn('[UTM Capture] localStorage not available:', e);
}
}
/**
* Get UTM data from localStorage (checks expiry)
* @returns {Object|null} Stored UTM data or null if expired/not found
*/
function getStoredUtm() {
try {
var stored = localStorage.getItem(UTM_STORAGE_KEY);
if (!stored) return null;
var data = JSON.parse(stored);
if (data.expires_at && new Date().getTime() > data.expires_at) {
localStorage.removeItem(UTM_STORAGE_KEY);
return null;
}
return data.params;
} catch (e) {
return null;
}
}
/**
* Main execution
*/
function init() {
var urlParams = getUrlParams();
var hasParams = Object.keys(urlParams).length > 0;
if (hasParams) {
// New UTM parameters detected - store them
storeInSession(urlParams);
storeInLocal(urlParams);
console.log('[UTM Capture] Parameters captured:', Object.keys(urlParams).join(', '));
} else {
// No new params - check if we have stored data to restore to session
var storedParams = getStoredUtm();
if (storedParams && Object.keys(storedParams).length > 0) {
// Restore to session storage for consistency
Object.keys(storedParams).forEach(function(key) {
if (!sessionStorage.getItem(key)) {
sessionStorage.setItem(key, storedParams[key]);
}
});
console.log('[UTM Capture] Restored from localStorage:', Object.keys(storedParams).join(', '));
}
}
}
// Run when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose utility function for other scripts
window.RockwellUTM = {
getParams: function() {
var params = {};
CAPTURE_PARAMS.forEach(function(key) {
var value = sessionStorage.getItem(key);
if (value) params[key] = value;
});
return params;
},
getStoredParams: getStoredUtm
};
})();
How can we help you today? Drop us a line below and our team will be with you. Don't forget to visit our Knowledge Base Articles Page for answers to commonly asked questions.
Contact form
Choosing a selection results in a full page refresh.
Opens in a new window.
tag
*
* DEPENDENCIES: Requires utm_capture.js to be loaded first
*
* HOW IT WORKS:
* 1. Intercepts add-to-cart events (form submit and Ajax)
* 2. Reads UTM params from sessionStorage
* 3. Updates cart attributes via Shopify Ajax API
* 4. Cart attributes persist through entire checkout flow
*
* @version 1.0.0
* @date 2026-01-14
* @project Rockwell Growth Marketing - Attribution Fix
*/
(function() {
'use strict';
// Configuration
var DEBUG = false; // Set to true for console logging
var CART_UPDATE_ENDPOINT = '/cart/update.js';
var ATTRIBUTE_PREFIX = ''; // Optional prefix for cart attributes
// UTM parameters to store in cart
var UTM_KEYS = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_content',
'utm_term',
'fbclid',
'gclid'
];
/**
* Get UTM parameters from storage
* @returns {Object} UTM parameters
*/
function getUtmParams() {
var params = {};
UTM_KEYS.forEach(function(key) {
var value = sessionStorage.getItem(key);
if (value) {
params[ATTRIBUTE_PREFIX + key] = value;
}
});
// Add metadata
var capturedAt = sessionStorage.getItem('utm_captured_at');
var landingPage = sessionStorage.getItem('utm_landing_page');
if (capturedAt) params[ATTRIBUTE_PREFIX + 'utm_captured_at'] = capturedAt;
if (landingPage) params[ATTRIBUTE_PREFIX + 'utm_landing_page'] = landingPage;
return params;
}
/**
* Check if we have any UTM data to store
* @returns {boolean}
*/
function hasUtmData() {
return UTM_KEYS.some(function(key) {
return sessionStorage.getItem(key);
});
}
/**
* Update cart attributes via Shopify Ajax API
* @returns {Promise}
*/
function updateCartAttributes() {
if (!hasUtmData()) {
if (DEBUG) console.log('[Cart Attributes] No UTM data to store');
return Promise.resolve();
}
var attributes = getUtmParams();
if (DEBUG) console.log('[Cart Attributes] Updating cart with:', attributes);
return fetch(CART_UPDATE_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ attributes: attributes })
})
.then(function(response) {
if (!response.ok) {
throw new Error('Cart update failed: ' + response.status);
}
return response.json();
})
.then(function(cart) {
if (DEBUG) console.log('[Cart Attributes] Cart updated successfully');
return cart;
})
.catch(function(error) {
console.warn('[Cart Attributes] Error updating cart:', error);
throw error;
});
}
/**
* Intercept form submissions for add-to-cart
*/
function interceptFormSubmit() {
document.addEventListener('submit', function(event) {
var form = event.target;
// Check if this is an add-to-cart form
if (form.action && form.action.indexOf('/cart/add') !== -1) {
if (DEBUG) console.log('[Cart Attributes] Add-to-cart form detected');
// Update cart attributes after form processes
// Using setTimeout to ensure cart is updated first
setTimeout(function() {
updateCartAttributes();
}, 500);
}
});
}
/**
* Intercept Ajax add-to-cart calls by patching fetch
*/
function interceptFetchCalls() {
var originalFetch = window.fetch;
window.fetch = function(url, options) {
var promise = originalFetch.apply(this, arguments);
// Check if this is a cart/add call
if (typeof url === 'string' && url.indexOf('/cart/add') !== -1) {
if (DEBUG) console.log('[Cart Attributes] Ajax add-to-cart detected');
// After the add-to-cart completes, update attributes
promise.then(function() {
setTimeout(function() {
updateCartAttributes();
}, 100);
}).catch(function() {
// Ignore errors in add-to-cart
});
}
return promise;
};
}
/**
* Intercept XMLHttpRequest add-to-cart calls
*/
function interceptXhrCalls() {
var originalXhrOpen = XMLHttpRequest.prototype.open;
var originalXhrSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._url = url;
return originalXhrOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function() {
var xhr = this;
if (xhr._url && xhr._url.indexOf('/cart/add') !== -1) {
if (DEBUG) console.log('[Cart Attributes] XHR add-to-cart detected');
xhr.addEventListener('load', function() {
if (xhr.status >= 200 && xhr.status < 300) {
setTimeout(function() {
updateCartAttributes();
}, 100);
}
});
}
return originalXhrSend.apply(this, arguments);
};
}
/**
* Update cart attributes immediately if cart already has items
*/
function updateExistingCart() {
if (!hasUtmData()) return;
// Check if cart has items
fetch('/cart.js')
.then(function(response) { return response.json(); })
.then(function(cart) {
if (cart.item_count > 0) {
if (DEBUG) console.log('[Cart Attributes] Cart has items, updating attributes');
updateCartAttributes();
}
})
.catch(function() {
// Ignore errors
});
}
/**
* Initialize cart attribute tracking
*/
function init() {
// Set up interception for future add-to-cart events
interceptFormSubmit();
interceptFetchCalls();
interceptXhrCalls();
// Update existing cart if it has items
updateExistingCart();
if (DEBUG) console.log('[Cart Attributes] Initialized');
}
// Run when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose utility functions
window.RockwellCartAttributes = {
update: updateCartAttributes,
getParams: getUtmParams,
hasData: hasUtmData
};
})();