BigCommerce Analytics: Stencil Handlebars, Script Manager, | OpsBlu Docs

BigCommerce Analytics: Stencil Handlebars, Script Manager,

Implement analytics on BigCommerce stores. Covers Script Manager injection, Stencil Handlebars data layers, webhook-based server-side tracking, and GA4...

Analytics Architecture on BigCommerce

BigCommerce is a SaaS ecommerce platform that uses the Stencil theme engine with Handlebars templates. Analytics tracking integrates through three mechanisms:

  • Script Manager is BigCommerce's built-in interface for adding tracking scripts globally or per-page-type, with placement control (head, footer) and consent category support
  • Stencil Handlebars templates expose product, category, and cart data through the {{inject}} helper and jsContext object, making structured data available to client-side scripts
  • Webhooks fire server-side HTTP callbacks on events like order creation, cart updates, and customer registration for server-side analytics
  • Storefront GraphQL provides client-side access to cart contents, customer data, and product details without additional API calls

BigCommerce controls the checkout flow. On standard (non-headless) stores, the checkout page uses BigCommerce's optimized checkout, which limits custom script injection. Analytics on the checkout and order confirmation pages requires specific approaches.

For headless stores using the BigCommerce API with a custom frontend (Next.js, Gatsby, etc.), all analytics scripts live in the frontend framework, not in BigCommerce. The patterns below apply to Stencil-based storefronts.


Installing Tracking Scripts

Script Manager is the primary method for adding analytics tags to BigCommerce stores. In the BigCommerce admin:

  1. Navigate to Channel Manager > Script Manager
  2. Click Create a Script
  3. Configure placement, page scope, and script content

For GTM, create a script with these settings:

  • Placement: Head
  • Location: All pages
  • Script category: Analytics
  • Script type: Script
<!-- GTM Head Script (Script Manager) -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>

Create a second script for the noscript fallback with Placement: Footer.

Script Manager scripts are injected by BigCommerce's server before the page reaches the browser, so they persist across theme updates and do not require template file editing.

Via Stencil Theme Templates

For more control over script placement, add tracking code directly in the theme's Handlebars templates:

{{!-- templates/layout/base.html --}}
<!DOCTYPE html>
<html>
<head>
  {{> head}}
  {{{head.scripts}}}

  <script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
      'page_type': '{{page_type}}',
      'currency_code': '{{currency_selector.active_currency_code}}'
    });
  </script>
</head>
<body>
  {{{header}}}
  {{{body}}}
  {{{footer}}}
  {{{foot.scripts}}}
</body>
</html>

Data Layer with Stencil Context

BigCommerce's Stencil framework uses the {{inject}} helper to pass server-side data to client-side JavaScript via the jsContext object. This is the proper way to build a data layer from product and category data.

Product Page Data Layer

{{!-- templates/pages/product.html --}}
{{inject "productId" product.id}}
{{inject "productName" product.title}}
{{inject "productPrice" product.price.without_tax.value}}
{{inject "productSku" product.sku}}
{{inject "productBrand" product.brand.name}}
{{inject "productCategory" product.category}}
{{inject "productAvailability" product.availability}}

<script>
  var context = this.context || JSON.parse('{{jsContext}}');
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    'event': 'view_item',
    'ecommerce': {
      'items': [{
        'item_id': context.productSku,
        'item_name': context.productName,
        'price': parseFloat(context.productPrice),
        'item_brand': context.productBrand,
        'item_category': context.productCategory
      }]
    }
  });
</script>

Category Page Data Layer

{{!-- templates/pages/category.html --}}
{{inject "categoryName" category.name}}
{{inject "categoryId" category.id}}

<script>
  var context = this.context || JSON.parse('{{jsContext}}');
  window.dataLayer = window.dataLayer || [];

  var items = [];
  {{#each category.products}}
    items.push({
      'item_id': '{{sku}}',
      'item_name': '{{name}}',
      'price': {{price.without_tax.value}},
      'item_list_name': context.categoryName,
      'index': {{@index}}
    });
  {{/each}}

  dataLayer.push({
    'event': 'view_item_list',
    'ecommerce': {
      'item_list_name': context.categoryName,
      'items': items
    }
  });
</script>

Ecommerce Tracking

Add to Cart

BigCommerce's Stencil framework fires a cart-item-add event through the storefront API. Hook into this in your theme's JavaScript:

// assets/js/theme/global/cart-analytics.js
import utils from '@bigcommerce/stencil-utils';

export default function trackCartEvents() {
  $('body').on('ajax-cart-item-add', function(event, data) {
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      'event': 'add_to_cart',
      'ecommerce': {
        'items': [{
          'item_id': data.product_id,
          'item_name': data.name || '',
          'quantity': data.quantity || 1
        }]
      }
    });
  });
}

Order Confirmation (Purchase Event)

The order confirmation page is rendered by BigCommerce's checkout system. Use the Storefront Analytics data that BigCommerce exposes on the confirmation page:

{{!-- templates/pages/order-confirmation.html --}}
<script>
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    'event': 'purchase',
    'ecommerce': {
      'transaction_id': '{{checkout.order.id}}',
      'value': {{checkout.order.total.value}},
      'currency': '{{checkout.order.currency_code}}',
      'tax': {{checkout.order.tax_total.value}},
      'shipping': {{checkout.order.shipping_cost_total.value}},
      'items': [
        {{#each checkout.order.items}}
        {
          'item_id': '{{sku}}',
          'item_name': '{{name}}',
          'price': {{price.value}},
          'quantity': {{quantity}}
        }{{#unless @last}},{{/unless}}
        {{/each}}
      ]
    }
  });
</script>

Server-Side Purchase Tracking via Webhooks

For reliable purchase tracking that does not depend on the browser, use BigCommerce webhooks to send events via the GA4 Measurement Protocol:

// webhook-handler.js (your server)
app.post('/webhooks/bigcommerce/order-created', async (req, res) => {
  const orderId = req.body.data.id;

  // Fetch full order from BigCommerce API
  const order = await fetch(
    `https://api.bigcommerce.com/stores/${STORE_HASH}/v2/orders/${orderId}`,
    { headers: { 'X-Auth-Token': BC_API_TOKEN } }
  ).then(r => r.json());

  // Send to GA4 Measurement Protocol
  await fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXX&api_secret=${API_SECRET}`,
    {
      method: 'POST',
      body: JSON.stringify({
        client_id: order.customer_message || 'server',
        events: [{
          name: 'purchase',
          params: {
            transaction_id: String(order.id),
            value: parseFloat(order.total_inc_tax),
            currency: order.currency_code
          }
        }]
      })
    }
  );

  res.sendStatus(200);
});

Register the webhook in BigCommerce under Settings > API Accounts or via the Management API with the store/order/created scope.


Checkout Tracking Limitations

BigCommerce's optimized checkout is a separate application from the Stencil storefront. Key constraints:

  • Script Manager scripts with "All pages" scope do load on checkout pages, but scripts scoped to specific page types may not
  • Custom Handlebars templates do not apply to the checkout flow (except the order confirmation page)
  • Checkout SDK (BigCommerce's open-source checkout) allows full customization including analytics, but requires building a custom checkout
  • Checkout page events (begin_checkout, add_payment_info, add_shipping_info) are best tracked via Script Manager scripts that listen for DOM changes on the checkout page

For the begin_checkout event, add a Script Manager script that fires when the checkout URL is detected:

<script>
  if (window.location.pathname.includes('/checkout')) {
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
      'event': 'begin_checkout'
    });
  }
</script>

Common Errors

Error Cause Fix
Scripts not loading on checkout Script Manager scope excludes checkout pages Set script scope to "All pages" or specifically include "Checkout"
Duplicate purchase events Both client-side and webhook tracking fire Use one method or deduplicate by transaction_id in GTM
Product data undefined in data layer Missing {{inject}} helpers in Stencil template Add inject helpers before the script block in the template
Cart event not firing Using native form submit instead of AJAX cart Listen for ajax-cart-item-add event, not form submission
Price shows with tax when it should not Using product.price.value instead of product.price.without_tax.value Use the explicit tax-included or tax-excluded price object
Scripts removed after theme update Code added directly to theme files gets overwritten Use Script Manager instead of editing theme templates
Order confirmation data empty Accessing order data before checkout object is available Wrap data layer push in a DOM ready check or use setTimeout
Webhook fires but GA4 shows no data Missing or wrong api_secret in Measurement Protocol call Generate API secret in GA4 Admin > Data Streams > Measurement Protocol
Multi-currency prices incorrect Data layer captures display currency, not base currency Use {{currency_selector.active_currency_code}} and convert in GA4
Storefront GraphQL returns null API token missing or CORS not configured for custom domain Verify storefront API token and allowed origins in BigCommerce settings

Performance Considerations

  • Script Manager placement: Use "Footer" placement for non-critical analytics scripts. Only GTM's head snippet needs "Head" placement for early loading
  • Stencil bundle size: Analytics JavaScript added to theme bundles increases the main JS file size. Use Script Manager for analytics code to keep it separate from the theme bundle
  • Lazy loading: BigCommerce's Stencil framework supports {{region}} helpers for lazy-loaded content. Defer analytics initialization for below-the-fold product carousels
  • CDN caching: BigCommerce serves storefront pages through Akamai CDN. Injected Script Manager scripts are included in the cached response, so they do not add extra network requests
  • Image optimization: Use BigCommerce's built-in image optimization ({{getImageSrcset}} helper) to serve properly sized images. Unoptimized product images are the top cause of poor LCP on BigCommerce stores
  • Third-party tag audit: BigCommerce stores commonly accumulate review widgets, chat tools, and retargeting pixels. Audit total third-party script weight with Chrome DevTools' Coverage tab