Set Up Inventory Tracking in Analytics | OpsBlu Docs

Set Up Inventory Tracking in Analytics

Diagnosing and fixing out-of-stock, low inventory, and product availability tracking issues to optimize merchandising and prevent lost sales.

What This Means

Inventory tracking issues occur when you're not properly monitoring product availability, stock levels, and out-of-stock events in your analytics. Without this data, you can't identify lost revenue opportunities, optimize inventory levels, or understand how stock availability impacts customer behavior.

Key Inventory Metrics:

  • Out-of-stock product views and attempted purchases
  • Low stock threshold alerts
  • Back-in-stock notifications and conversions
  • Product availability by variant (size, color, etc.)
  • Inventory impact on conversion rates

Impact Assessment

Business Impact

  • Lost Revenue Unknown: Can't quantify sales lost to stockouts
  • Inventory Mismanagement: Don't know which products to restock urgently
  • Poor Customer Experience: Customers encounter unavailable products
  • Missed Opportunities: Can't capture demand for out-of-stock items
  • Inefficient Marketing: Advertising products that are unavailable

Analytics Impact

  • Incomplete Funnel Data: Don't know how many users abandon due to stockouts
  • False Performance Metrics: Products appear unpopular when actually out of stock
  • Attribution Gaps: Can't separate availability issues from product quality issues
  • Poor Forecasting: Missing demand signals for inventory planning

Common Causes

Implementation Gaps

  • No tracking when users view out-of-stock products
  • Stock status not included in product data layer
  • Variant availability not tracked separately
  • Back-in-stock events not monitored

Data Quality Issues

  • Real-time inventory not synced with analytics
  • Stock status cached and outdated
  • Variant stock levels not granular enough
  • Third-party inventory systems not integrated

Technical Problems

  • AJAX cart updates don't reflect stock changes
  • Checkout allows adding out-of-stock items
  • Inventory webhooks not triggering events
  • Stock status updates delayed

How to Diagnose

Check for Stock Status Tracking

// Monitor product views with stock status
window.dataLayer = window.dataLayer || [];
const originalPush = dataLayer.push;

dataLayer.push = function(...args) {
  args.forEach(event => {
    if (event.event === 'view_item') {
      const items = event.ecommerce?.items || [];
      items.forEach(item => {
        console.log('Product View:', {
          name: item.item_name,
          stock_status: item.stock_status || 'MISSING',
          stock_quantity: item.stock_quantity || 'MISSING'
        });

        if (!item.stock_status) {
          console.error('Missing stock_status for:', item.item_name);
        }
      });
    }
  });
  return originalPush.apply(this, args);
};

Validate Stock Data in Data Layer

// Check if out-of-stock products are being tracked
function checkStockTracking() {
  const stockStatus = document.querySelector('.stock-status')?.textContent.toLowerCase();
  const isOutOfStock = stockStatus?.includes('out of stock') ||
                       stockStatus?.includes('unavailable');

  const viewItemEvents = dataLayer.filter(e => e.event === 'view_item');
  const latestEvent = viewItemEvents[viewItemEvents.length - 1];

  console.log('Current Stock Status:', stockStatus);
  console.log('Tracked Stock Status:', latestEvent?.ecommerce?.items?.[0]?.stock_status);

  if (isOutOfStock) {
    if (!latestEvent?.ecommerce?.items?.[0]?.stock_status) {
      console.error('Out-of-stock product viewed but not tracked!');
    } else {
      console.log('✓ Out-of-stock status tracked correctly');
    }
  }
}

checkStockTracking();

Check GA4 Custom Dimensions

Verify you have custom dimensions set up for:

  1. stock_status - In Stock, Out of Stock, Low Stock, Backorder
  2. stock_quantity - Numeric inventory level
  3. availability_date - When item will be back in stock
  4. variant_availability - Stock by size/color/variant

General Fixes

1. Track Stock Status in Product Views

// Include stock information in view_item events
function trackViewItem(product, stockInfo) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'view_item',
    ecommerce: {
      currency: 'USD',
      value: product.price,
      items: [{
        item_id: product.id,
        item_name: product.name,
        item_brand: product.brand,
        item_category: product.category,
        item_variant: product.variant,
        price: product.price,
        quantity: 1,
        // Stock information
        stock_status: stockInfo.status, // 'in_stock', 'out_of_stock', 'low_stock', 'backorder'
        stock_quantity: stockInfo.quantity,
        low_stock_threshold: stockInfo.lowThreshold,
        availability_date: stockInfo.availableDate
      }]
    },
    // Also as top-level for easier reporting
    stock_status: stockInfo.status,
    stock_quantity: stockInfo.quantity
  });
}

// On product page load
if (isProductPage()) {
  const product = getProductData();
  const stockInfo = getStockInfo();

  trackViewItem(product, stockInfo);
}

2. Track Out-of-Stock Interaction Events

// Custom event when user tries to add out-of-stock item
function trackOutOfStockAttempt(product) {
  dataLayer.push({
    event: 'out_of_stock_attempt',
    product_id: product.id,
    product_name: product.name,
    product_category: product.category,
    requested_quantity: product.requestedQuantity,
    stock_status: 'out_of_stock',
    user_action: 'add_to_cart_attempt'
  });
}

// Attach to disabled "Add to Cart" buttons
document.querySelectorAll('.add-to-cart[disabled]').forEach(button => {
  button.addEventListener('click', (e) => {
    e.preventDefault();

    const productData = {
      id: button.dataset.productId,
      name: button.dataset.productName,
      category: button.dataset.productCategory,
      requestedQuantity: parseInt(document.querySelector('[name="quantity"]')?.value || 1)
    };

    trackOutOfStockAttempt(productData);

    // Show notification
    alert('This item is currently out of stock. Sign up for back-in-stock notifications!');
  });
});

3. Track Inventory in Cart Events

// Include stock status in add_to_cart events
function trackAddToCart(product, quantity) {
  const stockInfo = getStockInfo(product.id, product.variant);

  // Verify stock availability
  if (quantity > stockInfo.quantity) {
    dataLayer.push({
      event: 'insufficient_stock',
      product_id: product.id,
      product_name: product.name,
      requested_quantity: quantity,
      available_quantity: stockInfo.quantity
    });

    alert(`Only ${stockInfo.quantity} available in stock`);
    return false;
  }

  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,
        price: product.price,
        quantity: quantity,
        stock_status: stockInfo.status,
        stock_quantity: stockInfo.quantity,
        stock_after_cart: stockInfo.quantity - quantity
      }]
    }
  });

  // Track low stock warning
  const remainingStock = stockInfo.quantity - quantity;
  if (remainingStock > 0 && remainingStock <= stockInfo.lowThreshold) {
    dataLayer.push({
      event: 'low_stock_trigger',
      product_id: product.id,
      product_name: product.name,
      remaining_quantity: remainingStock,
      threshold: stockInfo.lowThreshold
    });
  }

  return true;
}

4. Track Back-in-Stock Notifications

// Track when users sign up for back-in-stock alerts
function trackBackInStockSignup(product, email) {
  dataLayer.push({
    event: 'back_in_stock_signup',
    product_id: product.id,
    product_name: product.name,
    product_category: product.category,
    product_variant: product.variant,
    user_email: hashEmail(email), // Hash for privacy
    signup_date: new Date().toISOString()
  });

  // Save to database for later notification
  saveBackInStockRequest(product, email);
}

// Form submission handler
document.querySelector('#back-in-stock-form')?.addEventListener('submit', async (e) => {
  e.preventDefault();

  const email = document.querySelector('#stock-alert-email').value;
  const productData = {
    id: document.querySelector('[data-product-id]').dataset.productId,
    name: document.querySelector('.product-name').textContent,
    category: document.querySelector('[data-category]')?.dataset.category,
    variant: getSelectedVariant()
  };

  await trackBackInStockSignup(productData, email);

  alert('We\'ll notify you when this item is back in stock!');
});

// Track when back-in-stock notification is sent (server-side)
async function sendBackInStockNotification(productId, userEmail) {
  // Send email notification

  // Track notification sent
  await fetch('/api/analytics/back-in-stock-sent', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      product_id: productId,
      user_email: userEmail,
      sent_date: new Date().toISOString()
    })
  });
}

// Track conversion from back-in-stock notification
function trackBackInStockPurchase(orderId, productId) {
  dataLayer.push({
    event: 'back_in_stock_conversion',
    transaction_id: orderId,
    product_id: productId,
    conversion_type: 'back_in_stock_alert'
  });
}

5. Track Variant Availability

// Track stock for each product variant
function trackVariantChange(selectedVariant) {
  const stockInfo = getStockInfo(selectedVariant.productId, selectedVariant.id);

  dataLayer.push({
    event: 'variant_selected',
    product_id: selectedVariant.productId,
    product_name: selectedVariant.productName,
    variant_id: selectedVariant.id,
    variant_name: selectedVariant.name, // e.g., "Blue / Large"
    variant_sku: selectedVariant.sku,
    stock_status: stockInfo.status,
    stock_quantity: stockInfo.quantity,
    price: selectedVariant.price
  });

  // Update UI based on stock
  updateAddToCartButton(stockInfo);
}

// Listen for variant selection changes
document.querySelectorAll('select[name="variant"], input[name="variant"]').forEach(input => {
  input.addEventListener('change', (e) => {
    const selectedVariant = getSelectedVariantData();
    trackVariantChange(selectedVariant);
  });
});

6. Real-Time Stock Updates

// Monitor inventory changes in real-time
class InventoryMonitor {
  constructor() {
    this.checkInterval = 30000; // Check every 30 seconds
    this.productId = null;
    this.lastStockLevel = null;
  }

  start(productId) {
    this.productId = productId;
    this.lastStockLevel = getCurrentStockLevel(productId);

    this.intervalId = setInterval(() => {
      this.checkStockChanges();
    }, this.checkInterval);
  }

  async checkStockChanges() {
    const currentStock = await fetchCurrentStock(this.productId);

    if (currentStock !== this.lastStockLevel) {
      this.handleStockChange(this.lastStockLevel, currentStock);
      this.lastStockLevel = currentStock;
    }
  }

  handleStockChange(oldStock, newStock) {
    dataLayer.push({
      event: 'inventory_change',
      product_id: this.productId,
      previous_stock: oldStock,
      current_stock: newStock,
      stock_delta: newStock - oldStock,
      timestamp: new Date().toISOString()
    });

    // Update UI
    updateStockDisplay(newStock);

    // Warn if stock decreased
    if (newStock < oldStock) {
      console.warn(`Stock decreased from ${oldStock} to ${newStock}`);
    }

    // Alert if now out of stock
    if (oldStock > 0 && newStock === 0) {
      dataLayer.push({
        event: 'stock_depleted',
        product_id: this.productId,
        depleted_at: new Date().toISOString()
      });

      alert('This item just went out of stock!');
    }
  }

  stop() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
    }
  }
}

// Usage on product page
if (isProductPage()) {
  const productId = getProductId();
  const monitor = new InventoryMonitor();
  monitor.start(productId);

  // Stop monitoring when user leaves page
  window.addEventListener('beforeunload', () => monitor.stop());
}

7. Server-Side Inventory Event Tracking

// Node.js example - Track inventory events server-side
const express = require('express');
const router = express.Router();

// Track when inventory reaches low stock threshold
router.post('/api/inventory/check-threshold', async (req, res) => {
  const { productId, currentStock, threshold } = req.body;

  if (currentStock <= threshold) {
    // Send to GA4 Measurement Protocol
    await sendToGA4({
      client_id: 'server',
      events: [{
        name: 'low_stock_threshold',
        params: {
          product_id: productId,
          current_stock: currentStock,
          threshold: threshold,
          timestamp: new Date().toISOString()
        }
      }]
    });

    // Trigger internal alert
    await alertInventoryTeam(productId, currentStock);
  }

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

// Track when product goes out of stock
router.post('/api/inventory/stock-out', async (req, res) => {
  const { productId, lastSoldAt } = req.body;

  await sendToGA4({
    client_id: 'server',
    events: [{
      name: 'product_stock_out',
      params: {
        product_id: productId,
        last_sold_at: lastSoldAt,
        stock_out_at: new Date().toISOString()
      }
    }]
  });

  // Get back-in-stock subscribers
  const subscribers = await getBackInStockSubscribers(productId);

  res.json({
    success: true,
    subscribers_waiting: subscribers.length
  });
});

// Track when product is restocked
router.post('/api/inventory/restock', async (req, res) => {
  const { productId, previousStock, newStock } = req.body;

  await sendToGA4({
    client_id: 'server',
    events: [{
      name: 'product_restocked',
      params: {
        product_id: productId,
        previous_stock: previousStock,
        new_stock: newStock,
        stock_added: newStock - previousStock,
        restocked_at: new Date().toISOString()
      }
    }]
  });

  // Notify subscribers
  const subscribers = await getBackInStockSubscribers(productId);
  for (const subscriber of subscribers) {
    await sendBackInStockEmail(subscriber.email, productId);
  }

  res.json({
    success: true,
    notifications_sent: subscribers.length
  });
});

async function sendToGA4(data) {
  const measurementId = process.env.GA4_MEASUREMENT_ID;
  const apiSecret = process.env.GA4_API_SECRET;

  await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`, {
    method: 'POST',
    body: JSON.stringify(data)
  });
}

module.exports = router;

Platform-Specific Guides

Shopify

<!-- Product page - sections/product-template.liquid -->
<script>
// Get stock information from Shopify
var productStock = {
  status: {% if product.available %}'in_stock'{% else %}'out_of_stock'{% endif %},
  quantity: {{ product.selected_or_first_available_variant.inventory_quantity | default: 0 }},
  policy: '{{ product.selected_or_first_available_variant.inventory_policy }}',
  management: '{{ product.selected_or_first_available_variant.inventory_management }}'
};

// Determine stock status
if (productStock.quantity === 0 && productStock.policy === 'continue') {
  productStock.status = 'backorder';
} else if (productStock.quantity > 0 && productStock.quantity <= 10) {
  productStock.status = 'low_stock';
}

// Track product view with stock info
dataLayer.push({ ecommerce: null });
dataLayer.push({
  event: 'view_item',
  ecommerce: {
    currency: '{{ cart.currency.iso_code }}',
    value: {{ product.price | money_without_currency | remove: ',' }},
    items: [{
      item_id: '{{ product.selected_or_first_available_variant.sku | default: product.id }}',
      item_name: '{{ product.title | escape }}',
      item_brand: '{{ product.vendor | escape }}',
      item_category: '{{ product.type | escape }}',
      item_variant: '{{ product.selected_or_first_available_variant.title | escape }}',
      price: {{ product.price | money_without_currency | remove: ',' }},
      stock_status: productStock.status,
      stock_quantity: productStock.quantity
    }]
  },
  stock_status: productStock.status,
  stock_quantity: productStock.quantity
});

// Track variant changes
var variantSelectors = document.querySelectorAll('.product-form__variant-selector');
variantSelectors.forEach(function(selector) {
  selector.addEventListener('change', function() {
    var selectedVariantId = document.querySelector('[name="id"]').value;

    fetch('/products/{{ product.handle }}.js')
      .then(res => res.json())
      .then(data => {
        var variant = data.variants.find(v => v.id == selectedVariantId);

        var stockStatus = 'in_stock';
        if (!variant.available) {
          stockStatus = variant.inventory_policy === 'continue' ? 'backorder' : 'out_of_stock';
        } else if (variant.inventory_quantity > 0 && variant.inventory_quantity <= 10) {
          stockStatus = 'low_stock';
        }

        dataLayer.push({
          event: 'variant_selected',
          variant_id: variant.id,
          variant_sku: variant.sku,
          variant_name: variant.title,
          stock_status: stockStatus,
          stock_quantity: variant.inventory_quantity,
          price: variant.price / 100
        });
      });
  });
});

// Track out-of-stock attempts
{% unless product.available %}
document.querySelector('.product-form__submit').addEventListener('click', function(e) {
  e.preventDefault();

  dataLayer.push({
    event: 'out_of_stock_attempt',
    product_id: '{{ product.id }}',
    product_name: '{{ product.title | escape }}',
    user_action: 'add_to_cart_attempt'
  });

  alert('This item is currently out of stock. Sign up for restock notifications!');
});
{% endunless %}
</script>

<!-- Back-in-stock notification form -->
{% unless product.available %}
<div class="back-in-stock-form">
  <h3>Get notified when back in stock</h3>
  <form id="bis-form">
    <input type="email" name="email" placeholder="Your email" required>
    <input type="hidden" name="product_id" value="{{ product.id }}">
    <input type="hidden" name="variant_id" value="{{ product.selected_or_first_available_variant.id }}">
    <button type="submit">Notify Me</button>
  </form>
</div>

<script>
document.getElementById('bis-form').addEventListener('submit', async function(e) {
  e.preventDefault();

  var email = this.querySelector('[name="email"]').value;
  var productId = this.querySelector('[name="product_id"]').value;
  var variantId = this.querySelector('[name="variant_id"]').value;

  // Track signup
  dataLayer.push({
    event: 'back_in_stock_signup',
    product_id: productId,
    variant_id: variantId,
    product_name: '{{ product.title | escape }}'
  });

  // Submit to your back-in-stock service
  await fetch('/apps/back-in-stock/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, productId, variantId })
  });

  alert('Thanks! We\'ll email you when this item is back in stock.');
});
</script>
{% endunless %}

WooCommerce

// Add to functions.php or custom plugin

// Include stock status in product view tracking
add_action('woocommerce_after_single_product', function() {
    global $product;

    $stock_quantity = $product->get_stock_quantity();
    $stock_status = $product->get_stock_status(); // 'instock', 'outofstock', 'onbackorder'

    // Determine detailed stock status
    $detailed_status = $stock_status;
    if ($stock_status === 'instock' && $stock_quantity <= 10 && $stock_quantity > 0) {
        $detailed_status = 'low_stock';
    }
    ?>
    <script>
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
        event: 'view_item',
        ecommerce: {
            currency: '<?php echo get_woocommerce_currency(); ?>',
            value: <?php echo $product->get_price(); ?>,
            items: [{
                item_id: '<?php echo $product->get_sku() ?: $product->get_id(); ?>',
                item_name: '<?php echo esc_js($product->get_name()); ?>',
                price: <?php echo $product->get_price(); ?>,
                stock_status: '<?php echo $detailed_status; ?>',
                stock_quantity: <?php echo $stock_quantity ?? 0; ?>,
                backorders_allowed: <?php echo $product->backorders_allowed() ? 'true' : 'false'; ?>
            }]
        },
        stock_status: '<?php echo $detailed_status; ?>',
        stock_quantity: <?php echo $stock_quantity ?? 0; ?>
    });
    </script>
    <?php
});

// Track out-of-stock product views
add_action('wp_footer', function() {
    if (is_product()) {
        global $product;

        if (!$product->is_in_stock()) {
            ?>
            <script>
            dataLayer.push({
                event: 'out_of_stock_view',
                product_id: '<?php echo $product->get_id(); ?>',
                product_name: '<?php echo esc_js($product->get_name()); ?>',
                stock_status: '<?php echo $product->get_stock_status(); ?>'
            });

            // Track add to cart attempts on out-of-stock products
            document.querySelector('.single_add_to_cart_button')?.addEventListener('click', function(e) {
                <?php if (!$product->is_in_stock() && !$product->backorders_allowed()): ?>
                e.preventDefault();

                dataLayer.push({
                    event: 'out_of_stock_attempt',
                    product_id: '<?php echo $product->get_id(); ?>',
                    product_name: '<?php echo esc_js($product->get_name()); ?>'
                });

                alert('This product is currently out of stock.');
                <?php endif; ?>
            });
            </script>
            <?php
        }
    }
});

// Track low stock threshold alerts (run via cron)
add_action('woocommerce_low_stock', function($product) {
    // Log to analytics
    error_log('Low stock alert: ' . $product->get_name());

    // Send to GA4 via Measurement Protocol
    $data = [
        'client_id' => 'server',
        'events' => [[
            'name' => 'low_stock_threshold',
            'params' => [
                'product_id' => $product->get_id(),
                'product_name' => $product->get_name(),
                'stock_quantity' => $product->get_stock_quantity(),
                'threshold' => get_option('woocommerce_notify_low_stock_amount')
            ]
        ]]
    ];

    wp_remote_post('https://www.google-analytics.com/mp/collect?measurement_id=' . GA4_MEASUREMENT_ID . '&api_secret=' . GA4_API_SECRET, [
        'body' => json_encode($data),
        'headers' => ['Content-Type' => 'application/json']
    ]);
});

// Track when product goes out of stock
add_action('woocommerce_no_stock', function($product) {
    $data = [
        'client_id' => 'server',
        'events' => [[
            'name' => 'product_stock_out',
            'params' => [
                'product_id' => $product->get_id(),
                'product_name' => $product->get_name(),
                'stock_out_at' => current_time('mysql')
            ]
        ]]
    ];

    wp_remote_post('https://www.google-analytics.com/mp/collect?measurement_id=' . GA4_MEASUREMENT_ID . '&api_secret=' . GA4_API_SECRET, [
        'body' => json_encode($data),
        'headers' => ['Content-Type' => 'application/json']
    ]);
});

BigCommerce

// Add to theme/assets/js/theme/product.js
import PageManager from './page-manager';
import utils from '@bigcommerce/stencil-utils';

export default class Product extends PageManager {
    onReady() {
        this.trackProductStock();
        this.monitorStockChanges();
    }

    trackProductStock() {
        utils.api.product.getById(this.context.productId, { template: 'products/product-view' }, (err, response) => {
            if (response) {
                const $response = $(response);
                const stockLevel = parseInt($response.find('[data-stock-level]').data('stockLevel'));
                const stockStatus = this.getStockStatus(stockLevel);

                dataLayer.push({ ecommerce: null });
                dataLayer.push({
                    event: 'view_item',
                    ecommerce: {
                        items: [{
                            item_id: this.context.productId,
                            stock_status: stockStatus,
                            stock_quantity: stockLevel
                        }]
                    },
                    stock_status: stockStatus,
                    stock_quantity: stockLevel
                });
            }
        });
    }

    getStockStatus(quantity) {
        if (quantity === 0) return 'out_of_stock';
        if (quantity <= 10) return 'low_stock';
        return 'in_stock';
    }

    monitorStockChanges() {
        $('[data-product-option-change]').on('change', (event) => {
            utils.api.productAttributes.optionChange(this.context.productId, $(event.target).serialize(), (err, response) => {
                if (response) {
                    const stockLevel = response.data.stock;
                    const stockStatus = this.getStockStatus(stockLevel);

                    dataLayer.push({
                        event: 'variant_selected',
                        product_id: this.context.productId,
                        stock_status: stockStatus,
                        stock_quantity: stockLevel
                    });
                }
            });
        });
    }
}

Magento

<!-- Add to Magento_Catalog/templates/product/view.phtml -->
<?php
$_product = $block->getProduct();
$stockItem = $_product->getExtensionAttributes()->getStockItem();
$stockQty = $stockItem->getQty();
$isInStock = $stockItem->getIsInStock();

$stockStatus = 'out_of_stock';
if ($isInStock) {
    $stockStatus = $stockQty <= 10 ? 'low_stock' : 'in_stock';
}
?>

<script>
require(['jquery'], function($) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
        event: 'view_item',
        ecommerce: {
            currency: '<?php echo $block->getCurrency(); ?>',
            value: <?php echo $_product->getFinalPrice(); ?>,
            items: [{
                item_id: '<?php echo $_product->getSku(); ?>',
                item_name: '<?php echo $block->escapeJs($_product->getName()); ?>',
                price: <?php echo $_product->getFinalPrice(); ?>,
                stock_status: '<?php echo $stockStatus; ?>',
                stock_quantity: <?php echo (int)$stockQty; ?>
            }]
        },
        stock_status: '<?php echo $stockStatus; ?>',
        stock_quantity: <?php echo (int)$stockQty; ?>
    });

    <?php if (!$isInStock): ?>
    // Track out-of-stock product view
    dataLayer.push({
        event: 'out_of_stock_view',
        product_id: '<?php echo $_product->getId(); ?>',
        product_name: '<?php echo $block->escapeJs($_product->getName()); ?>'
    });
    <?php endif; ?>
});
</script>

Testing & Validation

Stock Tracking Checklist

  • Product Views: view_item includes stock_status and stock_quantity
  • Variant Changes: Stock updates when variant selected
  • Out-of-Stock Views: Custom event fires for unavailable products
  • Add to Cart: Stock checked before adding to cart
  • Low Stock Alerts: Tracked when inventory crosses threshold
  • Back-in-Stock: Signup events tracked properly
  • Stock Status Values: Consistent naming (in_stock, out_of_stock, low_stock, backorder)
  • Real-Time Updates: Stock changes reflected in analytics

GA4 Custom Dimensions Setup

Create these custom dimensions in GA4:

  1. stock_status (Event-scoped)

    • Description: Product stock status
    • Values: in_stock, out_of_stock, low_stock, backorder
  2. stock_quantity (Event-scoped)

    • Description: Current stock level
    • Type: Number
  3. stock_after_cart (Event-scoped)

    • Description: Remaining stock after add to cart
    • Type: Number

Console Validation

// Check if stock tracking is working
function validateStockTracking() {
  const viewItems = dataLayer.filter(e => e.event === 'view_item');
  const latestView = viewItems[viewItems.length - 1];

  console.log('Stock Tracking Validation:\n');

  if (!latestView) {
    console.error('✗ No view_item events found');
    return;
  }

  const item = latestView.ecommerce?.items?.[0];

  console.log('Stock Status:', item?.stock_status || 'MISSING');
  console.log('Stock Quantity:', item?.stock_quantity ?? 'MISSING');

  if (!item?.stock_status) {
    console.error('✗ Missing stock_status parameter');
  } else {
    console.log('✓ Stock status tracked');
  }

  if (typeof item?.stock_quantity === 'undefined') {
    console.error('✗ Missing stock_quantity parameter');
  } else {
    console.log('✓ Stock quantity tracked');
  }

  // Check for stock-related events
  const stockEvents = dataLayer.filter(e =>
    ['out_of_stock_attempt', 'low_stock_trigger', 'back_in_stock_signup'].includes(e.event)
  );

  console.log(`\nStock Events Found: ${stockEvents.length}`);
  stockEvents.forEach(e => console.log(`- ${e.event}`));
}

validateStockTracking();

Further Reading