Cart Abandonment Tracking | Blue Frog Docs

Cart Abandonment Tracking

Diagnosing and fixing cart abandonment tracking issues to recover lost revenue and understand customer behavior.

Cart Abandonment Tracking

What This Means

Cart abandonment tracking issues prevent you from identifying which customers leave products in their cart without completing a purchase. Without accurate abandonment data, you can't set up recovery campaigns, identify friction points, or measure the effectiveness of your cart optimization efforts.

Key Cart Events:

  • view_cart - Customer views cart
  • add_to_cart - Product added to cart
  • remove_from_cart - Product removed from cart
  • begin_checkout - Checkout initiated
  • Missing purchase event = Abandonment

Impact Assessment

Business Impact

  • Lost Recovery Revenue: Can't identify who to retarget with abandoned cart emails
  • Unknown Friction Points: Don't know where or why customers abandon
  • Wasted Ad Spend: Can't exclude converters from abandonment campaigns
  • Missed Opportunities: No data to optimize cart experience

Analytics Impact

  • Incomplete Funnel Data: Can't calculate abandonment rates
  • Poor Remarketing: Dynamic ads can't show abandoned products
  • Attribution Gaps: Missing connection between cart and purchase
  • Inaccurate LTV: Customer journey incomplete

Common Causes

Data Collection Issues

  • Cart events not firing when cart is updated
  • User identification missing (no client_id or user_id)
  • Cart data not persisting across sessions
  • Events firing but items array is empty

Technical Problems

  • AJAX cart updates don't trigger events
  • Cart drawer/modal interactions not tracked
  • Guest checkout loses cart attribution
  • Third-party cart apps interfere with tracking

Session Continuity

  • Cart created on mobile, abandoned on desktop (cross-device)
  • Cart events fire before GTM container loads
  • User clears cookies/storage between sessions
  • Session timeout resets cart tracking

How to Diagnose

Check Cart Event Flow

// Monitor all cart-related events
const cartEvents = ['view_cart', 'add_to_cart', 'remove_from_cart', 'begin_checkout', 'purchase'];
const eventLog = [];

window.dataLayer = window.dataLayer || [];
const originalPush = dataLayer.push;

dataLayer.push = function(...args) {
  args.forEach(event => {
    if (cartEvents.includes(event.event)) {
      const timestamp = new Date().toISOString();
      eventLog.push({
        timestamp,
        event: event.event,
        items: event.ecommerce?.items?.length || 0,
        value: event.ecommerce?.value || 0
      });
      console.log('🛒 Cart Event:', event.event, event.ecommerce);
    }
  });
  return originalPush.apply(this, args);
};

// Check for abandonment after 5 minutes
setTimeout(() => {
  const hasCart = eventLog.some(e => e.event === 'add_to_cart');
  const hasPurchase = eventLog.some(e => e.event === 'purchase');

  if (hasCart && !hasPurchase) {
    console.warn('⚠️ Cart Abandonment Detected!');
    console.table(eventLog);
  }
}, 300000);

GA4 Exploration Report

  1. Navigate to GA4 → Explore → Funnel Exploration
  2. Create funnel:
    • Step 1: add_to_cart
    • Step 2: begin_checkout
    • Step 3: purchase
  3. Check drop-off rates at each step
  4. Segment by device, traffic source, and user properties

Check User Identification

// Verify user can be tracked across sessions
console.log('Client ID:', window.gtag?.('get', 'GA_MEASUREMENT_ID', 'client_id'));
console.log('Session ID:', window.gtag?.('get', 'GA_MEASUREMENT_ID', 'session_id'));
console.log('User ID:', dataLayer.find(e => e.user_id)?.user_id);
console.log('Cookies:', document.cookie.includes('_ga'));

General Fixes

1. Track All Cart Interactions

Add to Cart:

// Fire on every add to cart action
function trackAddToCart(product, quantity = 1) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'add_to_cart',
    ecommerce: {
      currency: 'USD',
      value: product.price * quantity,
      items: [{
        item_id: product.id,
        item_name: product.name,
        item_brand: product.brand,
        item_category: product.category,
        price: product.price,
        quantity: quantity
      }]
    }
  });
}

// Attach to all add to cart buttons
document.querySelectorAll('[data-add-to-cart]').forEach(button => {
  button.addEventListener('click', (e) => {
    const productData = JSON.parse(e.target.dataset.product);
    const quantity = parseInt(e.target.closest('form')?.querySelector('[name="quantity"]')?.value || 1);
    trackAddToCart(productData, quantity);
  });
});

View Cart:

// Fire when cart page loads or cart drawer opens
function trackViewCart(cartData) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'view_cart',
    ecommerce: {
      currency: 'USD',
      value: cartData.total,
      items: cartData.items.map(item => ({
        item_id: item.id,
        item_name: item.name,
        item_brand: item.brand,
        item_category: item.category,
        price: item.price,
        quantity: item.quantity
      }))
    }
  });
}

// On cart page load
if (window.location.pathname.includes('/cart')) {
  trackViewCart(getCartData());
}

// On cart drawer open
const cartDrawer = document.querySelector('.cart-drawer');
const observer = new MutationObserver((mutations) => {
  if (cartDrawer.classList.contains('open')) {
    trackViewCart(getCartData());
  }
});
observer.observe(cartDrawer, { attributes: true, attributeFilter: ['class'] });

Remove from Cart:

// Track cart removals
function trackRemoveFromCart(product, quantity) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'remove_from_cart',
    ecommerce: {
      currency: 'USD',
      value: product.price * quantity,
      items: [{
        item_id: product.id,
        item_name: product.name,
        price: product.price,
        quantity: quantity
      }]
    }
  });
}

// Attach to remove buttons
document.querySelectorAll('[data-remove-from-cart]').forEach(button => {
  button.addEventListener('click', (e) => {
    const productData = JSON.parse(e.target.dataset.product);
    trackRemoveFromCart(productData, productData.quantity);
  });
});

2. Persist Cart Attribution Data

// Store cart session data for attribution
function saveCartSession() {
  const cartSession = {
    sessionId: getSessionId(),
    clientId: getClientId(),
    cartCreatedAt: new Date().toISOString(),
    items: getCartItems(),
    value: getCartTotal(),
    source: getTrafficSource(),
    medium: getTrafficMedium(),
    campaign: getCampaign()
  };

  // Save to localStorage for cross-session tracking
  localStorage.setItem('cart_session', JSON.stringify(cartSession));

  // Also save to session storage
  sessionStorage.setItem('cart_session', JSON.stringify(cartSession));
}

// Track when cart is created
window.addEventListener('cartUpdated', saveCartSession);

// On purchase, retrieve cart session data
function trackPurchaseWithAttribution(orderData) {
  const cartSession = JSON.parse(localStorage.getItem('cart_session'));

  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: orderData.id,
      value: orderData.total,
      tax: orderData.tax,
      shipping: orderData.shipping,
      currency: 'USD',
      items: orderData.items
    },
    // Attribution data from cart session
    cart_session_id: cartSession?.sessionId,
    time_in_cart: calculateTimeInCart(cartSession?.cartCreatedAt)
  });

  // Clear cart session data after purchase
  localStorage.removeItem('cart_session');
  sessionStorage.removeItem('cart_session');
}

function calculateTimeInCart(createdAt) {
  if (!createdAt) return null;
  const created = new Date(createdAt);
  const now = new Date();
  return Math.floor((now - created) / 1000 / 60); // minutes
}

3. Track Cross-Device Abandonment

// Use user_id for logged-in users
function setUserIdentity(userId, email) {
  // Set GA4 user_id
  gtag('config', 'GA_MEASUREMENT_ID', {
    user_id: userId
  });

  // Set in data layer
  dataLayer.push({
    user_id: userId,
    user_email: email // Hash this for privacy
  });

  // Set User ID in GTM
  dataLayer.push({
    event: 'user_login',
    user_id: userId
  });
}

// Call when user logs in or on page load if authenticated
if (isUserLoggedIn()) {
  setUserIdentity(getCurrentUserId(), getCurrentUserEmail());
}

4. Server-Side Cart Abandonment Tracking

// Node.js example - Track cart abandonment server-side
const { MongoClient } = require('mongodb');

async function saveCartForRecovery(cartData) {
  const client = await MongoClient.connect(process.env.MONGODB_URI);
  const db = client.db('analytics');

  await db.collection('abandoned_carts').insertOne({
    sessionId: cartData.sessionId,
    clientId: cartData.clientId,
    userId: cartData.userId,
    email: cartData.email,
    items: cartData.items,
    value: cartData.value,
    currency: cartData.currency,
    createdAt: new Date(),
    status: 'abandoned',
    source: cartData.source,
    medium: cartData.medium,
    campaign: cartData.campaign
  });

  await client.close();
}

// API endpoint to track cart updates
app.post('/api/cart/update', async (req, res) => {
  const cartData = req.body;

  // Save cart data
  await saveCartForRecovery(cartData);

  // Schedule abandonment check after 1 hour
  scheduleAbandonmentCheck(cartData.sessionId, 60 * 60 * 1000);

  res.json({ success: true });
});

// Check if cart was abandoned
async function checkAbandonmentStatus(sessionId) {
  const client = await MongoClient.connect(process.env.MONGODB_URI);
  const db = client.db('analytics');

  const cart = await db.collection('abandoned_carts').findOne({ sessionId });
  const order = await db.collection('orders').findOne({ sessionId });

  if (cart && !order) {
    // Cart was abandoned - trigger recovery email
    await triggerAbandonmentEmail(cart);
    await db.collection('abandoned_carts').updateOne(
      { sessionId },
      { $set: { status: 'recovery_sent', recoverySentAt: new Date() } }
    );
  } else if (order) {
    // Order completed - mark as converted
    await db.collection('abandoned_carts').updateOne(
      { sessionId },
      { $set: { status: 'converted', convertedAt: new Date() } }
    );
  }

  await client.close();
}

Platform-Specific Guides

Shopify

<!-- Add to theme.liquid -->
<script>
// Track cart drawer opening
document.addEventListener('DOMContentLoaded', function() {
  // Watch for cart drawer
  const cartDrawer = document.querySelector('.cart-drawer') || document.querySelector('#cart-drawer');

  if (cartDrawer) {
    const observer = new MutationObserver(() => {
      if (cartDrawer.classList.contains('active') || cartDrawer.style.display !== 'none') {
        fetch('/cart.js')
          .then(res => res.json())
          .then(cart => {
            dataLayer.push({ ecommerce: null });
            dataLayer.push({
              event: 'view_cart',
              ecommerce: {
                currency: '{{ cart.currency.iso_code }}',
                value: cart.total_price / 100,
                items: cart.items.map((item, index) => ({
                  item_id: item.sku || item.product_id,
                  item_name: item.product_title,
                  item_variant: item.variant_title,
                  price: item.price / 100,
                  quantity: item.quantity,
                  index: index
                }))
              }
            });
          });
      }
    });

    observer.observe(cartDrawer, { attributes: true, childList: true });
  }
});

// Save cart session for attribution
fetch('/cart.js')
  .then(res => res.json())
  .then(cart => {
    if (cart.item_count > 0) {
      localStorage.setItem('shopify_cart_session', JSON.stringify({
        token: cart.token,
        created_at: new Date().toISOString(),
        item_count: cart.item_count,
        total: cart.total_price / 100
      }));
    }
  });
</script>

WooCommerce

// Add to functions.php
add_action('woocommerce_add_to_cart', function($cart_item_key, $product_id, $quantity) {
    ?>
    <script>
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
        event: 'add_to_cart',
        ecommerce: {
            currency: '<?php echo get_woocommerce_currency(); ?>',
            value: <?php echo wc_get_product($product_id)->get_price() * $quantity; ?>,
            items: [{
                item_id: '<?php echo wc_get_product($product_id)->get_sku() ?: $product_id; ?>',
                item_name: '<?php echo esc_js(wc_get_product($product_id)->get_name()); ?>',
                price: <?php echo wc_get_product($product_id)->get_price(); ?>,
                quantity: <?php echo $quantity; ?>
            }]
        }
    });
    </script>
    <?php
}, 10, 3);

// Track cart abandonment server-side
add_action('wp_ajax_save_cart_session', 'save_cart_session');
add_action('wp_ajax_nopriv_save_cart_session', 'save_cart_session');

function save_cart_session() {
    $cart = WC()->cart;

    if (!$cart->is_empty()) {
        $cart_data = array(
            'session_id' => WC()->session->get_customer_id(),
            'user_id' => get_current_user_id(),
            'email' => wp_get_current_user()->user_email,
            'items' => array(),
            'total' => $cart->get_cart_contents_total(),
            'created_at' => current_time('mysql')
        );

        foreach ($cart->get_cart() as $cart_item) {
            $cart_data['items'][] = array(
                'product_id' => $cart_item['product_id'],
                'quantity' => $cart_item['quantity'],
                'price' => $cart_item['data']->get_price()
            );
        }

        // Save to custom table or send to external service
        update_user_meta(get_current_user_id(), 'abandoned_cart_data', $cart_data);
    }

    wp_send_json_success();
}

BigCommerce

// Add to theme/assets/js/theme/global.js
import utils from '@bigcommerce/stencil-utils';

// Track cart updates
utils.hooks.on('cart-item-add', (event, form) => {
    utils.api.cart.getCart({}, (err, response) => {
        if (response) {
            dataLayer.push({ ecommerce: null });
            dataLayer.push({
                event: 'add_to_cart',
                ecommerce: {
                    currency: response.currency.code,
                    value: response.baseAmount,
                    items: response.lineItems.physicalItems.map(item => ({
                        item_id: item.sku,
                        item_name: item.name,
                        price: item.salePrice,
                        quantity: item.quantity
                    }))
                }
            });

            // Save cart session
            localStorage.setItem('bc_cart_session', JSON.stringify({
                cartId: response.id,
                created_at: new Date().toISOString(),
                value: response.baseAmount
            }));
        }
    });
});

Magento

<!-- Add to Magento_Checkout/templates/cart.phtml -->
<script>
require(['jquery', 'Magento_Customer/js/customer-data'], function($, customerData) {
    var cart = customerData.get('cart');

    cart.subscribe(function(updatedCart) {
        if (updatedCart.items && updatedCart.items.length > 0) {
            var items = updatedCart.items.map(function(item) {
                return {
                    item_id: item.product_sku,
                    item_name: item.product_name,
                    price: parseFloat(item.product_price_value),
                    quantity: parseInt(item.qty)
                };
            });

            dataLayer.push({ ecommerce: null });
            dataLayer.push({
                event: 'view_cart',
                ecommerce: {
                    currency: updatedCart.subtotal.currency,
                    value: parseFloat(updatedCart.subtotal.value),
                    items: items
                }
            });

            // Save cart session
            localStorage.setItem('magento_cart_session', JSON.stringify({
                created_at: new Date().toISOString(),
                items: items.length,
                value: parseFloat(updatedCart.subtotal.value)
            }));
        }
    });
});
</script>

Testing & Validation

Cart Abandonment Test Flow

  1. Add Product to Cart

    • add_to_cart event fires
    • Event includes complete item data
    • Cart session data saved to localStorage
  2. View Cart

    • view_cart event fires
    • All cart items included in items array
    • Cart total matches sum of item values
  3. Begin Checkout

    • begin_checkout event fires
    • Session continuity maintained
  4. Abandon Cart

    • Cart data persists in storage
    • User identification present (client_id or user_id)
    • Can retrieve cart data after page refresh
  5. Return to Site

    • Original cart session can be linked to new session
    • Attribution data preserved

GA4 Validation

// Run this in console after going through cart flow
const cartEvents = dataLayer.filter(e =>
  ['add_to_cart', 'view_cart', 'remove_from_cart', 'begin_checkout'].includes(e.event)
);

console.log(`Total cart events: ${cartEvents.length}`);
console.table(cartEvents.map(e => ({
  event: e.event,
  items: e.ecommerce?.items?.length,
  value: e.ecommerce?.value
})));

// Check for missing data
cartEvents.forEach((e, i) => {
  if (!e.ecommerce?.items?.length) {
    console.error(`Event ${i} missing items array`);
  }
  if (!e.ecommerce?.currency) {
    console.error(`Event ${i} missing currency`);
  }
});

Further Reading

// SYS.FOOTER