GA4 Ecommerce Tracking on 3dcart (Shift4Shop) | OpsBlu Docs

GA4 Ecommerce Tracking on 3dcart (Shift4Shop)

Complete GA4 ecommerce tracking for 3dcart/Shift4Shop including product views, add to cart, checkout, and purchase events.

Implement comprehensive GA4 ecommerce tracking for your 3dcart/Shift4Shop store, including product impressions, detail views, cart actions, checkout steps, and purchase transactions.

Ecommerce Events Overview

GA4 uses specific event names and parameters for ecommerce tracking. Here's how they map to 3dcart/Shift4Shop:

Store Action GA4 Event Page Location Implementation
View category view_item_list Category pages Manual
View product view_item Product pages Manual
Add to cart add_to_cart All pages Manual/GTM
Remove from cart remove_from_cart Cart page Manual
View cart view_cart Cart page Manual
Begin checkout begin_checkout Checkout start Manual
Add payment info add_payment_info Checkout Manual
Purchase purchase Order confirmation Manual

Product Impressions (Category Pages)

Track when customers view product categories and listings.

Using 3dcart Template on Category Pages

Add this code to your category template or Global Footer with page detection:

<script>
  // Only fire on category pages
  if (window.location.pathname.indexOf('/category') !== -1 ||
      document.querySelector('.category-page, .product-listing')) {

    // Wait for page to load
    window.addEventListener('load', function() {
      var products = [];
      var productElements = document.querySelectorAll('.product-item, .product-box');

      productElements.forEach(function(element, index) {
        var productId = element.getAttribute('data-product-id') || '';
        var productName = element.querySelector('.product-name, .product-title');
        var productPrice = element.querySelector('.product-price, .price');

        if (productId || productName) {
          products.push({
            item_id: productId,
            item_name: productName ? productName.textContent.trim() : '',
            index: index,
            price: productPrice ? parseFloat(productPrice.textContent.replace(/[^0-9.]/g, '')) : 0,
            item_list_name: '[categoryname]',
            item_list_id: '[categoryid]'
          });
        }
      });

      if (products.length > 0) {
        gtag('event', 'view_item_list', {
          item_list_id: '[categoryid]',
          item_list_name: '[categoryname]',
          items: products.slice(0, 20) // Limit to first 20 products
        });
      }
    });
  }
</script>

Alternative: Hardcode Product Data

For more reliable tracking, hardcode product data in category template:

<script>
  gtag('event', 'view_item_list', {
    item_list_id: '[categoryid]',
    item_list_name: '[categoryname]',
    items: [
      {
        item_id: 'PRODUCT_ID_1',
        item_name: 'Product Name 1',
        price: 29.99,
        index: 0,
        item_list_name: '[categoryname]'
      },
      {
        item_id: 'PRODUCT_ID_2',
        item_name: 'Product Name 2',
        price: 39.99,
        index: 1,
        item_list_name: '[categoryname]'
      }
      // Add more products as needed
    ]
  });
</script>

Product Detail Views

Track when customers view individual product pages.

Product Page Implementation

Add to product template or Global Footer with page detection:

<script>
  // Only fire on product pages
  if (window.location.pathname.indexOf('/product') !== -1 ||
      document.querySelector('.product-page, .product-details')) {

    // Fire view_item event
    gtag('event', 'view_item', {
      currency: 'USD', // Change to your currency
      value: parseFloat('[productprice]'),
      items: [{
        item_id: '[productid]',
        item_name: '[productname]',
        price: parseFloat('[productprice]'),
        item_brand: '[manufacturer]', // If available
        item_category: '[categoryname]',
        quantity: 1
      }]
    });
  }
</script>

With Product Variants

If tracking product options/variants:

<script>
  if (window.location.pathname.indexOf('/product') !== -1) {
    // Get selected variant
    var variantSelect = document.querySelector('select[name="options"], .product-options select');
    var selectedVariant = variantSelect ? variantSelect.options[variantSelect.selectedIndex].text : '';

    gtag('event', 'view_item', {
      currency: 'USD',
      value: parseFloat('[productprice]'),
      items: [{
        item_id: '[productid]',
        item_name: '[productname]',
        item_variant: selectedVariant,
        price: parseFloat('[productprice]'),
        item_category: '[categoryname]',
        quantity: 1
      }]
    });
  }
</script>

Add to Cart

Track when items are added to the shopping cart.

Standard Add to Cart Tracking

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Find add to cart buttons (adjust selector for your theme)
    var addToCartForms = document.querySelectorAll('form[action*="addtocart"], .add-to-cart-form');

    addToCartForms.forEach(function(form) {
      form.addEventListener('submit', function(e) {
        // Get quantity
        var quantityInput = form.querySelector('input[name="quantity"]');
        var quantity = quantityInput ? parseInt(quantityInput.value) : 1;

        // Get product data
        var productId = form.querySelector('input[name="productid"]')?.value || '[productid]';
        var productPrice = parseFloat('[productprice]');

        // Fire add_to_cart event
        gtag('event', 'add_to_cart', {
          currency: 'USD',
          value: productPrice * quantity,
          items: [{
            item_id: productId,
            item_name: '[productname]',
            price: productPrice,
            quantity: quantity,
            item_category: '[categoryname]'
          }]
        });
      });
    });
  });
</script>

AJAX Add to Cart

If your theme uses AJAX cart functionality:

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Listen for AJAX cart add (adjust for your theme's AJAX method)
    var addToCartButtons = document.querySelectorAll('.ajax-add-to-cart, [data-ajax-cart]');

    addToCartButtons.forEach(function(button) {
      button.addEventListener('click', function() {
        var productId = button.getAttribute('data-product-id') || '[productid]';
        var productName = button.getAttribute('data-product-name') || '[productname]';
        var productPrice = button.getAttribute('data-product-price') || '[productprice]';
        var quantity = 1;

        // Get quantity from nearby input if exists
        var qtyInput = button.closest('.product-item')?.querySelector('input[name="quantity"]');
        if (qtyInput) {
          quantity = parseInt(qtyInput.value) || 1;
        }

        gtag('event', 'add_to_cart', {
          currency: 'USD',
          value: parseFloat(productPrice) * quantity,
          items: [{
            item_id: productId,
            item_name: productName,
            price: parseFloat(productPrice),
            quantity: quantity
          }]
        });
      });
    });
  });
</script>

Remove from Cart

Track when items are removed from the cart.

Cart Page Remove Tracking

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Only on cart page
    if (window.location.pathname.indexOf('/cart') !== -1) {

      // Find remove buttons
      var removeButtons = document.querySelectorAll('.remove-item, [data-remove-cart], a[href*="remove"]');

      removeButtons.forEach(function(button) {
        button.addEventListener('click', function() {
          // Get product data from cart row
          var cartRow = button.closest('tr, .cart-item');
          var productId = cartRow?.getAttribute('data-product-id') || '';
          var productName = cartRow?.querySelector('.product-name, .item-name')?.textContent || '';
          var productPrice = cartRow?.querySelector('.price, .item-price')?.textContent || '0';
          var quantity = cartRow?.querySelector('.quantity, input[name="quantity"]')?.value || '1';

          gtag('event', 'remove_from_cart', {
            currency: 'USD',
            value: parseFloat(productPrice.replace(/[^0-9.]/g, '')) * parseInt(quantity),
            items: [{
              item_id: productId,
              item_name: productName.trim(),
              price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
              quantity: parseInt(quantity)
            }]
          });
        });
      });
    }
  });
</script>

View Cart

Track when customers view their shopping cart.

Cart Page View Tracking

<script>
  // Only on cart page
  if (window.location.pathname.indexOf('/cart') !== -1) {

    window.addEventListener('load', function() {
      var cartItems = [];
      var cartRows = document.querySelectorAll('.cart-item, table.cart tbody tr');

      cartRows.forEach(function(row) {
        var productId = row.getAttribute('data-product-id') ||
                       row.querySelector('[data-product-id]')?.getAttribute('data-product-id') || '';
        var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
        var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
        var quantity = row.querySelector('input[name="quantity"]')?.value || '1';

        if (productName) {
          cartItems.push({
            item_id: productId,
            item_name: productName.trim(),
            price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
            quantity: parseInt(quantity)
          });
        }
      });

      // Get cart total
      var cartTotalElement = document.querySelector('.cart-total, .total-amount, [data-cart-total]');
      var cartTotal = cartTotalElement ? parseFloat(cartTotalElement.textContent.replace(/[^0-9.]/g, '')) : 0;

      if (cartItems.length > 0) {
        gtag('event', 'view_cart', {
          currency: 'USD',
          value: cartTotal,
          items: cartItems
        });
      }
    });
  }
</script>

Begin Checkout

Track when customers start the checkout process.

Checkout Page Tracking

<script>
  // Fire on checkout page load
  if (window.location.pathname.indexOf('/checkout') !== -1 ||
      window.location.pathname.indexOf('/onepage') !== -1) {

    window.addEventListener('load', function() {
      // Extract cart data (you may need to adjust based on your checkout template)
      var checkoutItems = [];

      // Method 1: Parse from checkout display
      var itemRows = document.querySelectorAll('.checkout-item, .order-item');
      itemRows.forEach(function(row) {
        var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
        var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
        var quantity = row.querySelector('.quantity, .qty')?.textContent || '1';

        if (productName) {
          checkoutItems.push({
            item_name: productName.trim(),
            price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
            quantity: parseInt(quantity.replace(/[^0-9]/g, ''))
          });
        }
      });

      // Get order total
      var orderTotal = document.querySelector('.order-total, .total-amount');
      var total = orderTotal ? parseFloat(orderTotal.textContent.replace(/[^0-9.]/g, '')) : 0;

      if (checkoutItems.length > 0) {
        gtag('event', 'begin_checkout', {
          currency: 'USD',
          value: total,
          items: checkoutItems
        });
      }
    });
  }
</script>

Alternative: Trigger from Cart Page

Track when "Checkout" button is clicked on cart page:

<script>
  document.addEventListener('DOMContentLoaded', function() {
    var checkoutButton = document.querySelector('.checkout-button, [href*="checkout"], button[name="checkout"]');

    if (checkoutButton) {
      checkoutButton.addEventListener('click', function() {
        // Get cart data
        var cartTotal = parseFloat('[carttotal]');

        gtag('event', 'begin_checkout', {
          currency: 'USD',
          value: cartTotal,
          items: [
            // Add items array if available
          ]
        });
      });
    }
  });
</script>

Add Payment Info

Track when customers enter payment information.

Payment Step Tracking

<script>
  // Detect when on payment step of checkout
  if (window.location.pathname.indexOf('/checkout') !== -1) {

    // Listen for payment form interaction
    var paymentForm = document.querySelector('form[name="payment"], .payment-form');

    if (paymentForm) {
      var paymentTracked = false;

      paymentForm.addEventListener('submit', function() {
        if (!paymentTracked) {
          paymentTracked = true;

          var orderTotal = document.querySelector('.order-total, .total-amount');
          var total = orderTotal ? parseFloat(orderTotal.textContent.replace(/[^0-9.]/g, '')) : 0;

          gtag('event', 'add_payment_info', {
            currency: 'USD',
            value: total,
            payment_type: 'credit_card', // Or detect actual payment method
            items: [
              // Add items if available
            ]
          });
        }
      });
    }
  }
</script>

Purchase Event (Order Confirmation)

Track completed purchases on the order confirmation/receipt page.

Order Confirmation Tracking

Add to Global Footer with page detection or directly in receipt template:

<script>
  // Only fire on order confirmation page
  if (window.location.pathname.indexOf('/receipt') !== -1 ||
      window.location.pathname.indexOf('/thankyou') !== -1 ||
      document.querySelector('.order-confirmation, .receipt-page')) {

    // Use 3dcart template variables for transaction data
    gtag('event', 'purchase', {
      transaction_id: '[invoicenumber]',
      affiliation: 'Online Store',
      value: parseFloat('[invoicetotal]'),
      currency: 'USD',
      tax: parseFloat('[invoicetax]'),
      shipping: parseFloat('[invoiceshipping]'),
      items: [
        // See below for items array implementation
      ]
    });
  }
</script>

Complete Purchase Tracking with Items

For complete tracking with all order items:

<script>
  if (window.location.pathname.indexOf('/receipt') !== -1) {

    // Parse order items from receipt page
    var orderItems = [];
    var itemRows = document.querySelectorAll('.order-item, .receipt-item, table.order tbody tr');

    itemRows.forEach(function(row) {
      var productName = row.querySelector('.product-name, .item-name')?.textContent || '';
      var productSku = row.querySelector('.sku, .product-sku')?.textContent || '';
      var productPrice = row.querySelector('.price, .item-price')?.textContent || '0';
      var quantity = row.querySelector('.quantity, .qty')?.textContent || '1';

      if (productName) {
        orderItems.push({
          item_id: productSku.trim(),
          item_name: productName.trim(),
          price: parseFloat(productPrice.replace(/[^0-9.]/g, '')),
          quantity: parseInt(quantity.replace(/[^0-9]/g, ''))
        });
      }
    });

    // Fire purchase event
    gtag('event', 'purchase', {
      transaction_id: '[invoicenumber]',
      affiliation: 'Online Store',
      value: parseFloat('[invoicetotal]'),
      currency: 'USD',
      tax: parseFloat('[invoicetax]'),
      shipping: parseFloat('[invoiceshipping]'),
      coupon: '[couponcode]', // If coupon was used
      items: orderItems
    });

    // Prevent duplicate tracking on page refresh
    sessionStorage.setItem('purchase_tracked_[invoicenumber]', 'true');
  }
</script>

Prevent Duplicate Purchase Events

Add deduplication to prevent multiple purchase events on page refresh:

<script>
  if (window.location.pathname.indexOf('/receipt') !== -1) {
    var orderId = '[invoicenumber]';
    var storageKey = 'purchase_tracked_' + orderId;

    // Only fire if not already tracked
    if (!sessionStorage.getItem(storageKey)) {

      gtag('event', 'purchase', {
        transaction_id: orderId,
        value: parseFloat('[invoicetotal]'),
        currency: 'USD',
        tax: parseFloat('[invoicetax]'),
        shipping: parseFloat('[invoiceshipping]'),
        items: [
          // Items array
        ]
      });

      // Mark as tracked
      sessionStorage.setItem(storageKey, 'true');
    }
  }
</script>

GTM Implementation

For Google Tag Manager implementation of ecommerce events:

Create Data Layer on Product Pages

<script>
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    'event': 'view_item',
    'ecommerce': {
      'currency': 'USD',
      'value': parseFloat('[productprice]'),
      'items': [{
        'item_id': '[productid]',
        'item_name': '[productname]',
        'price': parseFloat('[productprice]'),
        'item_category': '[categoryname]',
        'quantity': 1
      }]
    }
  });
</script>

Create GTM Triggers and Tags

  1. Create Trigger:

    • Type: Custom Event
    • Event name: view_item
  2. Create GA4 Event Tag:

    • Event Name: view_item
    • Configuration Tag: Select your GA4 Config tag
    • Ecommerce Data: Use Data Layer variables
  3. Repeat for other ecommerce events

See GTM Data Layer Guide for complete GTM setup.

Testing Ecommerce Tracking

Use GA4 DebugView

  1. Enable Debug Mode:

    • Add ?debug_mode=true to URL
    • Or install GA Debugger extension
  2. Test Complete Funnel:

    • View category page → Check view_item_list
    • View product page → Check view_item
    • Add to cart → Check add_to_cart
    • View cart → Check view_cart
    • Start checkout → Check begin_checkout
    • Complete purchase → Check purchase
  3. Verify Event Parameters:

    • Check items array structure
    • Verify currency and values
    • Confirm transaction_id is unique

Browser Console Testing

// Check dataLayer
console.log(window.dataLayer);

// Manually fire test event
gtag('event', 'purchase', {
  'transaction_id': 'TEST123',
  'value': 99.99,
  'currency': 'USD',
  'items': [{'item_id': 'TEST', 'item_name': 'Test Product', 'price': 99.99, 'quantity': 1}]
});

Common Issues

Missing Transaction IDs

Problem: Purchase events without transaction_id

Fix: Ensure [invoicenumber] template variable is used on receipt page:

transaction_id: '[invoicenumber]'

Incorrect Currency Values

Problem: Values show with currency symbols ($99.99) instead of numbers

Fix: Parse currency values:

value: parseFloat('[invoicetotal]') // Correct
value: '[invoicetotal]' // Wrong

Items Array Empty

Problem: Ecommerce events fire but items array is empty

Fix: Verify items are properly constructed and product data is available on the page

Duplicate Purchase Events

Problem: Multiple purchase events for same transaction

Fix: Use sessionStorage deduplication (see example above)

Next Steps

Additional Resources