Inventory & Stock Tracking | Blue Frog Docs

Inventory & Stock Tracking

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

Inventory & Stock Tracking

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

// SYS.FOOTER