Skip to content

Instantly share code, notes, and snippets.

@mikenewbuild
Last active January 30, 2023 14:48
Show Gist options
  • Save mikenewbuild/23518e279367fe11a71f76b9be1e7ca8 to your computer and use it in GitHub Desktop.
Save mikenewbuild/23518e279367fe11a71f76b9be1e7ca8 to your computer and use it in GitHub Desktop.
Shopify GA4 ecom events
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ id }}');</script>
<!-- End Google Tag Manager -->
<script>
(() => {
try {
const shop_name = {{ shop.name | json }};
const page_type = {{ request.page_type | json }};
const currency = {{ cart.currency.iso_code | json }};
const cartKey = 'newbuildCart';
const googleChannelIdFormat = 'shopify_GB_{pid}_{vid}';
function setCart(cart) {
localStorage.setItem(cartKey, JSON.stringify(cart));
return getCart();
}
async function refreshCart() {
await fetch('/cart.js')
.then(res => res.json())
.then(cart => setCart(cart))
return getCart();
}
function getCart() {
const storedCart = localStorage.getItem(cartKey);
let cart = (storedCart != null) ? JSON.parse(storedCart) : {{ cart | json }};
return cart;
}
async function diffCart() {
let result = {
added: [],
removed: [],
};
let oldCart = getCart();
let newCart = await refreshCart();
let oldCartItems = oldCart?.items || [];
let newCartItems = newCart?.items || [];
const sameLine = (a, b) => a.key == b.key;
const newQuantity = (a, b) => a.key == b.key && a.quantity != b.quantity;
const onlyInLeft = (left, right, compare) => left.filter(leftValue => !right.some(rightValue => compare(leftValue, rightValue)));
result.added = onlyInLeft(newCartItems, oldCartItems, sameLine);
result.removed = onlyInLeft(oldCartItems, newCartItems, sameLine);
newCartItems.forEach(newItem => {
const oldItem = oldCartItems.find(item => newQuantity(item, newItem));
if (!oldItem) return;
const quantityChange = newItem.quantity - oldItem.quantity;
let updatedItem = { ...newItem };
updatedItem.quantity = Math.abs(quantityChange);
quantityChange > 0
? result.added.push(updatedItem)
: result.removed.push(updatedItem)
});
return result;
}
async function handleRequest(url) {
const isCartRequest = /\/cart\//.test(url);
if (isCartRequest) recordCartChange();
}
function observeFetch() {
const fetchObserver = new PerformanceObserver((list) => {
const monitorTypes = ['xmlhttprequest', 'fetch'];
for (const entry of list.getEntries()) {
if (monitorTypes.includes(entry.initiatorType)) {
handleRequest(entry.name);
}
}
});
fetchObserver.observe({ entryTypes: ["resource"] });
}
const formatMoney = (number) => (number/100.0).toFixed(2);
const ecom = (event, data) => {
dataLayer.push({ ecommerce: null });
dataLayer.push({ event, ecommerce: data });
};
function formatLineItem(item) {
return {
affiliation: shop_name,
item_id: item.product_id,
item_name: item.product_title,
item_brand: item.vendor,
item_category: item.product_type,
item_variant: item.variant_title,
currency: currency,
price: formatMoney(item.original_price),
discount: formatMoney(item.total_discount),
quantity: item.quantity
};
}
async function recordCartChange() {
const { added, removed } = await diffCart();
added.forEach(recordAddToCart);
removed.forEach(recordRemoveFromCart);
}
function recordUpdateCartItem(eventName, item) {
ecom(eventName, { items: [formatLineItem(item)] });
}
function recordAddToCart(item) {
recordUpdateCartItem('add_to_cart', item);
}
function recordRemoveFromCart(item) {
recordUpdateCartItem('remove_from_cart', item);
}
function recordProductView(product, variant, price) {
ecom("view_item", {
items: [
{
affiliation: shop_name,
item_id: product.id,
item_name: product.title,
item_variant: variant.title,
item_brand: product.vendor,
item_category: product.type,
currency: currency,
price: formatMoney(price),
}
]
});
}
function recordCollectionView(collection, products) {
const items = products.map(product => {
return {
affiliation: shop_name,
item_id: product.id,
item_name: product.title,
item_brand: product.vendor,
item_category: product.type,
item_list_id: collection.id,
item_list_name: collection.title,
currency: currency,
price: formatMoney(product.price),
};
});
ecom("view_item_list", { items });
}
function recordCartView(cart) {
ecom('view_cart', {
currency: currency,
value: formatMoney(cart.total_price),
items: cart.items.map(item => formatLineItem(item)),
});
}
function formatRemarketingIds(product) {
const variantIds = product.variants.map(v => v.id);
const placeholder = googleChannelIdFormat.replace('{pid}', product.id);
const googleChannelIds = variantIds.map(id => placeholder.replace('{vid}', id));
return [product.id, ...variantIds, ...googleChannelIds];
}
function recordRemarketingEvent(products, page_type) {
const page_types = [
'home',
'searchresults',
'category',
'product',
'basket',
];
const ecomm_pagetype = page_types.includes(page_type) ? page_type : 'other';
const ecomm_prodid = products.map(product => formatRemarketingIds(product));
const prices = products.map(product => product.price);
const ecomm_totalvalue = formatMoney(prices.reduce((acc, price) => acc + price, 0));
dataLayer.push({
event: 'fireRemarketingTag',
google_tag_params: { ecomm_pagetype, ecomm_prodid, ecomm_totalvalue }
});
}
if (page_type == 'product') {
const product = {{ product | json }};
const variant = {{ product.selected_or_first_available_variant | json }};
const price = {{ product.selected_or_first_available_variant.price | json }};
recordProductView(product, variant, price);
recordRemarketingEvent([product], 'product');
}
if (page_type == 'collection') {
const collection = {{ collection | json }};
const products = {{ collection.products | json }};
recordCollectionView(collection, products);
recordRemarketingEvent(products, 'category');
}
if (page_type == 'cart') {
const cart = {{ cart | json }};
recordCartView(cart);
recordCartChange();
const products = cart.items.map(item => ({ id: item.product_id, price: item.price }));
recordRemarketingEvent(products, 'basket');
}
refreshCart();
observeFetch();
} catch (err) {
console.error('[gtm]');
console.error(error);
}
})(dataLayer);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment