This script is designed for Shopify to track cart-related events using the Klaviyo tracking API. It detects when items are added, removed, or their quantity is adjusted in the cart and sends event data to Klaviyo.
- Fetches Cart Data: Retrieves the latest cart state from Shopify.
- Tracks Changes: Detects when items are:
- Added
- Removed
- Quantity Reduced
- Cart Emptied
- Includes Product Details: Captures key details such as:
- Product ID, Name, Price, Quantity
- Image URL, Product URL, Collections
- Currency and Variant Information
- Intercepts Cart Updates: Listens for changes by overriding:
fetch
requestsXMLHttpRequest
(AJAX requests)
- Sends Klaviyo Events: Logs each cart update with event-specific details:
Added to Cart
Removed from Cart
Quantity Reduced
Cart Emptied
- Provides real-time cart tracking for analytics.
- Enables personalized marketing automation using Klaviyo.
- Ensures accurate event logging for customer behavior insights.
- In Shopify, navigate to Online Store > Themes.
- Find your theme and click Customize.
- Click the three dots at the top and select Edit code.
- Open the theme.liquid file.
- Paste the provided snippet after all other code, before the closing
</body>
tag.
<script>
document.addEventListener('DOMContentLoaded', () => {
const klaviyo = window.klaviyo || [];
let lastCartState = null;
// Fetch the latest cart data from Shopify
const fetchCartData = async () => {
try {
const response = await fetch(`${window.location.origin}/cart.js`);
if (!response.ok) throw new Error('Failed to fetch cart');
return await response.json();
} catch (error) {
console.error('Error fetching cart data:', error);
return null;
}
};
// Retrieve collections for a given product
const getCollectionsForProduct = (productId) => window.productCollections?.[productId] || [];
// Format a single cart item to include ImageURL and Collections
const formatCartItem = (item, cartCurrency) => ({
ProductID: item.product_id || item.id,
Name: item.product_title,
Price: item.final_line_price / 100,
Quantity: item.quantity,
Currency: cartCurrency,
VariantID: item.variant_id || null,
ImageURL: item.image?.src || '', // Restore Image URL
Collections: getCollectionsForProduct(item.product_id) // Restore Collections
});
// Convert full cart data into an array of formatted items
const formatCartItems = (cart) => cart.items.map(item => formatCartItem(item, cart.currency));
// Track cart-related events
const trackEvent = (eventName, data) => {
console.log(`${eventName} Event Tracked:`, data);
klaviyo.push(['track', eventName, data]);
};
// Compare previous and current cart states to detect changes
const compareCartStates = (prevCart, newCart) => {
const prevCartMap = new Map(prevCart.items.map(item => [item.id, item]));
const newCartMap = new Map(newCart.items.map(item => [item.id, item]));
let addedItems = [];
let removedItems = [];
let quantityReducedItems = [];
// Detect newly added or increased quantity items
newCart.items.forEach(newItem => {
const prevItem = prevCartMap.get(newItem.id);
if (!prevItem || newItem.quantity > prevItem.quantity) {
addedItems.push(newItem);
}
});
// Detect removed or quantity-reduced items
prevCart.items.forEach(prevItem => {
const newItem = newCartMap.get(prevItem.id);
if (!newItem) {
removedItems.push(prevItem);
} else if (newItem.quantity < prevItem.quantity) {
quantityReducedItems.push({
...prevItem,
quantity: prevItem.quantity - newItem.quantity,
unit_price: prevItem.final_line_price / prevItem.quantity / 100
});
}
});
return { addedItems, removedItems, quantityReducedItems };
};
// Record and track cart changes
const recordCartChange = async () => {
const newCart = await fetchCartData();
if (!newCart) return;
const cartValue = newCart.total_price / 100;
const pageURL = window.location.href;
const formattedItems = formatCartItems(newCart);
// Track "Cart Emptied" event
if (newCart.items.length === 0) {
trackEvent('Cart Emptied', { total_price: 0, $value: 0, Currency: newCart.currency, PageURL: pageURL, items: [] });
}
if (lastCartState) {
const { addedItems, removedItems, quantityReducedItems } = compareCartStates(lastCartState, newCart);
addedItems.forEach(item => trackEvent('Added to Cart', {
...formatCartItem(item, newCart.currency), // Restore ImageURL & Collections
ProductURL: item.url,
Brand: item.vendor,
Price: item.final_line_price / item.quantity / 100,
CompareAtPrice: item.compare_at_price / 100,
PresentmentCurrency: newCart.currency,
VariantTitle: item.variant_title || '',
items: formattedItems,
$value: cartValue,
RecordedEventURL: pageURL
}));
removedItems.forEach(item => trackEvent('Removed from Cart', {
...formatCartItem(item, newCart.currency), // Restore ImageURL & Collections
ProductURL: item.url,
Price: item.final_line_price / item.quantity / 100,
Quantity: item.quantity,
items: formattedItems,
$value: cartValue,
RecordedEventURL: pageURL
}));
quantityReducedItems.forEach(item => trackEvent('Quantity Reduced', {
...formatCartItem(item, newCart.currency), // Restore ImageURL & Collections
ProductURL: item.url,
Price: item.unit_price,
items: formattedItems,
$value: cartValue,
RecordedEventURL: pageURL
}));
}
lastCartState = newCart;
};
// Intercept cart API requests to detect changes
const interceptCartChanges = () => {
const originalFetch = window.fetch.bind(window);
window.fetch = async (...args) => {
const response = await originalFetch(...args);
if (response.url.includes(`${window.location.origin}/cart/`)) await recordCartChange();
return response;
};
const originalXHR = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url) {
if (url.includes('/cart/')) {
this.addEventListener('load', async () => await recordCartChange());
}
return originalXHR.apply(this, arguments);
};
};
// Initialize cart tracking
(async () => {
lastCartState = await fetchCartData();
interceptCartChanges();
})();
});
</script>