Promotion & Coupon Tracking
What This Means
Promotion and coupon tracking issues occur when GA4 doesn't properly record promotional banners, discount codes, and special offers used on your site. Without accurate promotion data, you can't measure ROI of marketing campaigns, understand which offers drive conversions, or optimize your promotional strategy.
Key Promotion Events:
view_promotion- Promotional banner/offer is viewedselect_promotion- User clicks on a promotionadd_to_cart,begin_checkout,purchase- Includecouponparameter when discount applied
Impact Assessment
Business Impact
- Campaign ROI Unknown: Can't measure which promotions drive revenue
- Budget Waste: Spending on ineffective promotions without knowing
- Coupon Abuse: Can't track coupon usage patterns or detect fraud
- Lost Attribution: Don't know which offers influenced purchases
Analytics Impact
- Incomplete Revenue Data: Can't separate discounted vs. full-price sales
- Missing Promotion Reports: GA4 promotion reports are empty
- Attribution Gaps: Can't credit promotions for conversions
- Poor Segmentation: Can't analyze customer behavior by promotion type
Common Causes
Implementation Issues
view_promotionevent not implemented- Coupon codes not passed to e-commerce events
- Promotion creative_name and creative_slot missing
- Events fire without promotion_id or promotion_name
Technical Problems
- Coupon applied but not captured in data layer
- Third-party coupon tools bypass tracking
- Auto-applied discounts not tracked
- Multiple coupons combined but only one tracked
Data Quality Issues
- Coupon codes inconsistent (uppercase/lowercase)
- Free shipping treated as coupon code
- Promotion names not standardized
- Creative slots not uniquely identified
How to Diagnose
Check for Promotion Events
// Monitor all promotion events
const promoEvents = ['view_promotion', 'select_promotion'];
const trackedPromotions = new Set();
window.dataLayer = window.dataLayer || [];
const originalPush = dataLayer.push;
dataLayer.push = function(...args) {
args.forEach(event => {
// Check promotion-specific events
if (promoEvents.includes(event.event)) {
console.log('๐ Promotion Event:', event.event, event.ecommerce);
const items = event.ecommerce?.items || [];
items.forEach(promo => {
const key = `${promo.promotion_id}_${promo.creative_slot}`;
if (trackedPromotions.has(key)) {
console.warn('โ ๏ธ Duplicate promotion:', promo.promotion_name);
}
trackedPromotions.add(key);
});
}
// Check for coupon in e-commerce events
if (event.ecommerce?.coupon) {
console.log('๐๏ธ Coupon Applied:', {
event: event.event,
coupon: event.ecommerce.coupon,
value: event.ecommerce.value
});
}
});
return originalPush.apply(this, args);
};
Validate Coupon Tracking
// Test if coupons are being tracked properly
function validateCouponTracking() {
// Check cart/checkout events for coupon parameter
const ecomEvents = dataLayer.filter(e =>
['add_to_cart', 'begin_checkout', 'add_payment_info', 'purchase'].includes(e.event)
);
const withCoupon = ecomEvents.filter(e => e.ecommerce?.coupon);
const withoutCoupon = ecomEvents.filter(e => !e.ecommerce?.coupon);
console.log('E-commerce events:', ecomEvents.length);
console.log('With coupon:', withCoupon.length);
console.log('Without coupon:', withoutCoupon.length);
// Check if active coupon exists on page
const activeCoupon = document.querySelector('.coupon-code')?.textContent ||
document.querySelector('[data-coupon-code]')?.dataset.couponCode;
if (activeCoupon && withoutCoupon.length > 0) {
console.error('โ ๏ธ Coupon applied but not tracked in events!');
console.log('Active coupon:', activeCoupon);
}
}
validateCouponTracking();
GA4 DebugView Checklist
- Banner Impressions:
view_promotionfires when promo visible - Banner Clicks:
select_promotionfires on click - Coupon Usage: E-commerce events include
couponparameter - Required Fields: All promotions have
promotion_idandpromotion_name - Creative Attribution:
creative_nameandcreative_slotpresent
General Fixes
1. Track Promotion Impressions
// Track when promotional banner is viewed
function trackViewPromotion(promotions) {
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'view_promotion',
ecommerce: {
items: promotions.map(promo => ({
promotion_id: promo.id,
promotion_name: promo.name,
creative_name: promo.creativeName,
creative_slot: promo.slot,
location_id: promo.location
}))
}
});
}
// Track banners when they enter viewport
const promoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !entry.target.dataset.tracked) {
entry.target.dataset.tracked = 'true';
const promotion = {
id: entry.target.dataset.promoId,
name: entry.target.dataset.promoName,
creativeName: entry.target.dataset.creativeName,
slot: entry.target.dataset.slot,
location: entry.target.dataset.location
};
trackViewPromotion([promotion]);
}
});
}, {
threshold: 0.5
});
// Observe all promotional banners
document.querySelectorAll('[data-promotion]').forEach(banner => {
promoObserver.observe(banner);
});
2. Track Promotion Clicks
// Track when user clicks a promotional banner
function trackSelectPromotion(promotion) {
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'select_promotion',
ecommerce: {
items: [{
promotion_id: promotion.id,
promotion_name: promotion.name,
creative_name: promotion.creativeName,
creative_slot: promotion.slot,
location_id: promotion.location
}]
}
});
}
// Attach to all promotion links/buttons
document.querySelectorAll('[data-promotion]').forEach(element => {
element.addEventListener('click', (e) => {
const promotion = {
id: element.dataset.promoId,
name: element.dataset.promoName,
creativeName: element.dataset.creativeName,
slot: element.dataset.slot,
location: element.dataset.location
};
trackSelectPromotion(promotion);
});
});
3. Track Coupon Application
// Track when coupon is applied to cart
function trackCouponApplication(couponCode, discountAmount) {
// Store coupon for use in other e-commerce events
sessionStorage.setItem('applied_coupon', couponCode);
sessionStorage.setItem('discount_amount', discountAmount);
// Fire custom event
dataLayer.push({
event: 'coupon_applied',
coupon_code: couponCode,
discount_amount: discountAmount
});
console.log('โ Coupon applied:', couponCode);
}
// Listen for coupon form submission
document.querySelector('#coupon-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const couponCode = document.querySelector('#coupon-code').value.trim().toUpperCase();
// Apply coupon via API
const response = await fetch('/api/apply-coupon', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: couponCode })
});
const result = await response.json();
if (result.success) {
trackCouponApplication(couponCode, result.discountAmount);
updateCartDisplay();
} else {
console.error('Coupon failed:', result.error);
}
});
4. Include Coupon in E-commerce Events
// Helper to get current coupon
function getCurrentCoupon() {
return sessionStorage.getItem('applied_coupon') || '';
}
// Include coupon in all e-commerce events
function trackAddToCart(product, quantity) {
const coupon = getCurrentCoupon();
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'USD',
value: product.price * quantity,
coupon: coupon, // Include coupon
items: [{
item_id: product.id,
item_name: product.name,
price: product.price,
quantity: quantity,
coupon: coupon // Also at item level
}]
}
});
}
function trackBeginCheckout(cartData) {
const coupon = getCurrentCoupon();
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: 'USD',
value: cartData.total,
coupon: coupon,
items: cartData.items.map(item => ({
...item,
coupon: coupon
}))
}
});
}
function trackPurchase(orderData) {
const coupon = getCurrentCoupon();
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: orderData.id,
value: orderData.total,
tax: orderData.tax,
shipping: orderData.shipping,
currency: 'USD',
coupon: coupon,
items: orderData.items.map(item => ({
...item,
coupon: coupon
}))
}
});
// Clear coupon after purchase
sessionStorage.removeItem('applied_coupon');
sessionStorage.removeItem('discount_amount');
}
5. Track Multiple Coupons
// Handle multiple coupons applied to single order
function trackMultipleCoupons(coupons) {
// GA4 only accepts single coupon string
// Concatenate multiple coupons with delimiter
const couponString = coupons.join(',');
sessionStorage.setItem('applied_coupons', couponString);
// Also track each coupon individually in custom event
dataLayer.push({
event: 'multiple_coupons_applied',
coupons: coupons,
total_discount: coupons.reduce((sum, c) => sum + c.amount, 0)
});
}
// Use in e-commerce events
function getAppliedCoupons() {
return sessionStorage.getItem('applied_coupons') || '';
}
6. Track Auto-Applied Discounts
// Track automatic discounts (no coupon code needed)
function trackAutoDiscount(discountInfo) {
// Use special code for auto discounts
const couponCode = `AUTO_${discountInfo.type.toUpperCase()}`;
sessionStorage.setItem('applied_coupon', couponCode);
dataLayer.push({
event: 'auto_discount_applied',
discount_type: discountInfo.type,
discount_name: discountInfo.name,
discount_amount: discountInfo.amount,
auto_discount_code: couponCode
});
}
// Example: Free shipping over $50
if (cartTotal >= 50) {
trackAutoDiscount({
type: 'free_shipping',
name: 'Free Shipping Over $50',
amount: 0 // or calculated shipping cost
});
}
// Example: Buy 2 Get 1 Free
if (hasBogo) {
trackAutoDiscount({
type: 'bogo',
name: 'Buy 2 Get 1 Free',
amount: bogoDiscountAmount
});
}
7. Standardize Promotion Naming
// Create consistent promotion naming convention
const PROMOTION_TYPES = {
banner: {
homepage_hero: (name) => ({
id: `banner_homepage_hero`,
name: name,
creativeName: name,
slot: 'homepage_hero',
location: 'homepage'
}),
sidebar: (name, page) => ({
id: `banner_${page}_sidebar`,
name: name,
creativeName: name,
slot: 'sidebar',
location: page
}),
popup: (name) => ({
id: `popup_${name.toLowerCase().replace(/\s+/g, '_')}`,
name: name,
creativeName: name,
slot: 'popup',
location: 'sitewide'
})
},
coupon: {
percentage: (code, percent) => `${code}_${percent}OFF`,
fixed: (code, amount) => `${code}_$${amount}OFF`,
freeShipping: (code) => `${code}_FREESHIP`,
bogo: (code) => `${code}_BOGO`
}
};
// Usage
const heroPromo = PROMOTION_TYPES.banner.homepage_hero('Summer Sale 2025');
trackViewPromotion([heroPromo]);
const couponCode = PROMOTION_TYPES.coupon.percentage('SUMMER25', 25);
trackCouponApplication(couponCode, discountAmount);
Platform-Specific Guides
Shopify
<!-- Theme.liquid - Track promotional banners -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track homepage hero banner
{% if template == 'index' and section.settings.hero_promo_enabled %}
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'view_promotion',
ecommerce: {
items: [{
promotion_id: 'banner_homepage_hero',
promotion_name: '{{ section.settings.hero_promo_text | escape }}',
creative_name: '{{ section.settings.hero_promo_text | escape }}',
creative_slot: 'homepage_hero',
location_id: 'homepage'
}]
}
});
{% endif %}
// Track promotion banner clicks
document.querySelectorAll('[data-promotion-banner]').forEach(banner => {
banner.addEventListener('click', function() {
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'select_promotion',
ecommerce: {
items: [{
promotion_id: this.dataset.promoId,
promotion_name: this.dataset.promoName,
creative_name: this.dataset.creativeName,
creative_slot: this.dataset.slot,
location_id: window.location.pathname
}]
}
});
});
});
});
// Track discount code application
(function() {
const originalFetch = window.fetch;
window.fetch = function(...args) {
const [url, options] = args;
// Intercept discount code API calls
if (url.includes('/discount/') || url.includes('/cart/update')) {
return originalFetch.apply(this, args).then(response => {
return response.clone().json().then(data => {
// Check if discount was applied
if (data.cart && data.cart.total_discount > 0) {
const discountCode = document.querySelector('[name="discount"]')?.value ||
data.cart.cart_level_discount_applications[0]?.title ||
'UNKNOWN';
sessionStorage.setItem('shopify_discount', discountCode);
dataLayer.push({
event: 'coupon_applied',
coupon_code: discountCode,
discount_amount: data.cart.total_discount / 100
});
}
return response;
});
});
}
return originalFetch.apply(this, args);
};
})();
// Include discount in checkout events
document.addEventListener('DOMContentLoaded', function() {
const discountCode = sessionStorage.getItem('shopify_discount') || '';
// This would be included in your existing checkout tracking
window.trackBeginCheckout = function(cart) {
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: '{{ cart.currency.iso_code }}',
value: cart.total_price / 100,
coupon: discountCode,
items: cart.items.map(item => ({
item_id: item.sku || item.id,
item_name: item.title,
price: item.price / 100,
quantity: item.quantity,
coupon: discountCode
}))
}
});
};
});
</script>
WooCommerce
// Add to functions.php or custom plugin
// Track coupon application
add_action('woocommerce_applied_coupon', function($coupon_code) {
?>
<script>
sessionStorage.setItem('woo_coupon', '<?php echo esc_js($coupon_code); ?>');
dataLayer.push({
event: 'coupon_applied',
coupon_code: '<?php echo esc_js($coupon_code); ?>',
discount_amount: <?php
$coupon = new WC_Coupon($coupon_code);
echo $coupon->get_amount();
?>
});
</script>
<?php
});
// Track coupon removal
add_action('woocommerce_removed_coupon', function($coupon_code) {
?>
<script>
sessionStorage.removeItem('woo_coupon');
dataLayer.push({
event: 'coupon_removed',
coupon_code: '<?php echo esc_js($coupon_code); ?>'
});
</script>
<?php
});
// Include coupon in purchase event
add_action('woocommerce_thankyou', function($order_id) {
$order = wc_get_order($order_id);
$coupons = $order->get_coupon_codes();
$coupon_code = !empty($coupons) ? implode(',', $coupons) : '';
?>
<script>
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '<?php echo $order->get_order_number(); ?>',
value: <?php echo $order->get_total(); ?>,
tax: <?php echo $order->get_total_tax(); ?>,
shipping: <?php echo $order->get_shipping_total(); ?>,
currency: '<?php echo $order->get_currency(); ?>',
coupon: '<?php echo esc_js($coupon_code); ?>',
items: [
<?php
foreach ($order->get_items() as $item) {
$product = $item->get_product();
?>
{
item_id: '<?php echo $product->get_sku() ?: $product->get_id(); ?>',
item_name: '<?php echo esc_js($item->get_name()); ?>',
price: <?php echo $item->get_total() / $item->get_quantity(); ?>,
quantity: <?php echo $item->get_quantity(); ?>,
coupon: '<?php echo esc_js($coupon_code); ?>'
},
<?php
}
?>
]
}
});
</script>
<?php
});
// Track promotional banners (in template file)
?>
<div class="promo-banner"
data-promotion
data-promo-id="banner_sale_2025"
data-promo-name="Spring Sale 2025"
data-creative-name="Spring Sale Banner"
data-slot="homepage_top"
data-location="homepage">
<a href="/sale">Spring Sale - 30% Off!</a>
</div>
<script>
// Banner impression tracking
document.querySelectorAll('[data-promotion]').forEach(banner => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !entry.target.dataset.tracked) {
entry.target.dataset.tracked = 'true';
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'view_promotion',
ecommerce: {
items: [{
promotion_id: entry.target.dataset.promoId,
promotion_name: entry.target.dataset.promoName,
creative_name: entry.target.dataset.creativeName,
creative_slot: entry.target.dataset.slot,
location_id: entry.target.dataset.location
}]
}
});
}
});
}, { threshold: 0.5 });
observer.observe(banner);
});
</script>
BigCommerce
// Add to theme/assets/js/theme/global.js
import utils from '@bigcommerce/stencil-utils';
class PromotionTracking {
constructor() {
this.trackedPromotions = new Set();
this.init();
}
init() {
this.trackPromotionBanners();
this.trackCouponApplication();
}
trackPromotionBanners() {
const banners = document.querySelectorAll('[data-promotion-banner]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const banner = entry.target;
const promoId = banner.dataset.promoId;
if (!this.trackedPromotions.has(promoId)) {
this.trackedPromotions.add(promoId);
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'view_promotion',
ecommerce: {
items: [{
promotion_id: promoId,
promotion_name: banner.dataset.promoName,
creative_name: banner.dataset.creativeName,
creative_slot: banner.dataset.slot
}]
}
});
}
}
});
}, { threshold: 0.5 });
banners.forEach(banner => observer.observe(banner));
}
trackCouponApplication() {
// Monitor cart for coupon application
const cartObserver = setInterval(() => {
utils.api.cart.getCart({}, (err, response) => {
if (response && response.coupons && response.coupons.length > 0) {
const couponCode = response.coupons[0].code;
const lastCoupon = sessionStorage.getItem('bc_last_coupon');
if (couponCode !== lastCoupon) {
sessionStorage.setItem('bc_last_coupon', couponCode);
dataLayer.push({
event: 'coupon_applied',
coupon_code: couponCode,
discount_amount: response.coupons[0].discountedAmount
});
}
}
});
}, 2000);
}
getCurrentCoupon(callback) {
utils.api.cart.getCart({}, (err, response) => {
const coupon = response?.coupons?.[0]?.code || '';
callback(coupon);
});
}
}
export default new PromotionTracking();
Magento
<!-- Add to Magento_Checkout/templates/cart/coupon.phtml -->
<script>
require(['jquery', 'Magento_Customer/js/customer-data'], function($, customerData) {
var cart = customerData.get('cart');
// Track when coupon is applied
$(document).on('ajax:addToCart', function(e, data) {
if (data.coupon_code) {
sessionStorage.setItem('magento_coupon', data.coupon_code);
dataLayer.push({
event: 'coupon_applied',
coupon_code: data.coupon_code,
discount_amount: data.discount_amount
});
}
});
// Monitor cart for coupon changes
cart.subscribe(function(updatedCart) {
if (updatedCart.coupon_code) {
sessionStorage.setItem('magento_coupon', updatedCart.coupon_code);
}
});
});
</script>
<!-- Add to Magento_Checkout/templates/success.phtml -->
<?php
$order = $block->getOrder();
$couponCode = $order->getCouponCode();
?>
<script>
require(['jquery'], function($) {
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: '<?php echo $order->getIncrementId(); ?>',
value: <?php echo $order->getGrandTotal(); ?>,
tax: <?php echo $order->getTaxAmount(); ?>,
shipping: <?php echo $order->getShippingAmount(); ?>,
currency: '<?php echo $order->getOrderCurrencyCode(); ?>',
coupon: '<?php echo $couponCode ? esc_js($couponCode) : ''; ?>',
items: [
<?php foreach ($order->getAllVisibleItems() as $item): ?>
{
item_id: '<?php echo $item->getSku(); ?>',
item_name: '<?php echo esc_js($item->getName()); ?>',
price: <?php echo $item->getPrice(); ?>,
quantity: <?php echo $item->getQtyOrdered(); ?>,
coupon: '<?php echo $couponCode ? esc_js($couponCode) : ''; ?>'
},
<?php endforeach; ?>
]
}
});
});
</script>
Testing & Validation
Promotion Tracking Checklist
- Banner Impressions:
view_promotionfires when banner visible - Banner Clicks:
select_promotionfires on click - Coupon Application: Custom event fires when coupon applied
- Coupon in Cart:
add_to_cartincludes coupon parameter - Coupon in Checkout:
begin_checkoutincludes coupon - Coupon in Purchase:
purchaseincludes coupon - Multiple Coupons: All coupons tracked (concatenated)
- Auto Discounts: Automatic discounts tracked with special codes
- Required Fields: All promotions have id, name, creative_name, creative_slot
GA4 Reports to Check
Monetization โ E-commerce purchases โ Coupon
- Verify coupon codes appear
- Check revenue by coupon
Advertising โ All campaigns โ Promotions
- Verify promotion names appear
- Check clicks and conversions
Engagement โ Events โ view_promotion
- Verify promotion impressions tracked
- Check promotion_name parameter
Console Test Script
// Apply a test coupon and track the flow
async function testCouponTracking(testCoupon = 'TEST25') {
console.log('๐งช Testing coupon tracking...\n');
// Simulate coupon application
sessionStorage.setItem('applied_coupon', testCoupon);
// Test add to cart
console.log('1. Testing add_to_cart with coupon...');
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'add_to_cart',
ecommerce: {
coupon: testCoupon,
items: [{ item_id: 'TEST', item_name: 'Test Product', price: 10 }]
}
});
// Check if it was tracked
const cartEvent = dataLayer.filter(e => e.event === 'add_to_cart').pop();
if (cartEvent?.ecommerce?.coupon === testCoupon) {
console.log('โ Coupon tracked in add_to_cart');
} else {
console.error('โ Coupon NOT tracked in add_to_cart');
}
// Cleanup
sessionStorage.removeItem('applied_coupon');
}
testCouponTracking();