Last active
January 30, 2023 14:48
-
-
Save mikenewbuild/23518e279367fe11a71f76b9be1e7ca8 to your computer and use it in GitHub Desktop.
Shopify GA4 ecom events
This file contains 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
<!-- 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