GA4 Ecommerce Tracking on Shopware | OpsBlu Docs

GA4 Ecommerce Tracking on Shopware

Implement enhanced ecommerce tracking for Google Analytics 4 on Shopware 6 stores.

This guide covers how to implement GA4's enhanced ecommerce tracking on Shopware 6 to track the complete customer journey from product views to purchase.

Prerequisites: GA4 must be installed. See Install Google Analytics 4

GA4 Ecommerce Events Overview

GA4 tracks the following ecommerce events:

Event Trigger Purpose
view_item_list Category/search results Track product impressions
select_item Click on product Track product clicks
view_item Product detail page Track product views
add_to_cart Add to cart button Track cart additions
remove_from_cart Remove from cart Track cart removals
view_cart Cart page view Track cart views
begin_checkout Checkout start Track checkout initiation
add_shipping_info Shipping selected Track checkout progress
add_payment_info Payment selected Track checkout progress
purchase Order completed Track transactions

Implementation Methods

Method 1: Using Shopware Plugin

Many GA4 plugins for Shopware include built-in ecommerce tracking:

  1. Install GA4 Plugin

    • See GA4 Setup
    • Choose a plugin with ecommerce support
  2. Enable Ecommerce Tracking

    • Go to plugin configuration
    • Enable "Enhanced Ecommerce" option
    • Configure product ID source (SKU, product number, or ID)
    • Set currency settings
  3. Test Events

    • Navigate through your store
    • Check GA4 DebugView for events
    • Verify all product data is passed correctly

Method 2: Manual Implementation

For complete control, implement tracking manually in your theme.

Product List Views (view_item_list)

Track when users see product listings on category or search pages.

Category Page Tracking

{# In listing template: src/Resources/views/storefront/page/product-detail/index.html.twig #}
{% block page_product_listing %}
    {{ parent() }}

    <script>
        (function() {
            {% if page.listing.elements %}
                const items = [
                    {% for product in page.listing.elements %}
                    {
                        item_id: '{{ product.productNumber }}',
                        item_name: '{{ product.translated.name|e('js') }}',
                        item_brand: '{{ product.manufacturer.translated.name|default('')|e('js') }}',
                        item_category: '{{ product.categories.first.translated.name|default('')|e('js') }}',
                        price: {{ product.calculatedPrice.unitPrice }},
                        currency: '{{ context.currency.isoCode }}',
                        index: {{ loop.index0 }}
                    }{% if not loop.last %},{% endif %}
                    {% endfor %}
                ];

                if (typeof gtag !== 'undefined' && items.length > 0) {
                    gtag('event', 'view_item_list', {
                        item_list_id: '{{ page.category.id|default('search') }}',
                        item_list_name: '{{ page.category.translated.name|default('Search Results')|e('js') }}',
                        items: items
                    });
                }
            {% endif %}
        })();
    </script>
{% endblock %}

Product Clicks (select_item)

Track when users click on products in listings:

{% block component_product_box %}
    {{ parent() }}

    <script>
        (function() {
            const productBox = document.querySelector('[data-product-id="{{ product.id }}"]');

            if (productBox) {
                productBox.addEventListener('click', function() {
                    if (typeof gtag !== 'undefined') {
                        gtag('event', 'select_item', {
                            item_list_id: '{{ listingId|default('') }}',
                            item_list_name: '{{ listingName|default('')|e('js') }}',
                            items: [{
                                item_id: '{{ product.productNumber }}',
                                item_name: '{{ product.translated.name|e('js') }}',
                                item_brand: '{{ product.manufacturer.translated.name|default('')|e('js') }}',
                                item_category: '{{ product.categories.first.translated.name|default('')|e('js') }}',
                                price: {{ product.calculatedPrice.unitPrice }}
                            }]
                        });
                    }
                });
            }
        })();
    </script>
{% endblock %}

Product Detail Views (view_item)

Track product detail page views:

{# In product detail template #}
{% block page_product_detail %}
    {{ parent() }}

    <script>
        (function() {
            {% set product = page.product %}

            if (typeof gtag !== 'undefined') {
                gtag('event', 'view_item', {
                    currency: '{{ context.currency.isoCode }}',
                    value: {{ product.calculatedPrice.unitPrice }},
                    items: [{
                        item_id: '{{ product.productNumber }}',
                        item_name: '{{ product.translated.name|e('js') }}',
                        item_brand: '{{ product.manufacturer.translated.name|default('')|e('js') }}',
                        item_category: '{{ product.categories.first.translated.name|default('')|e('js') }}',
                        item_variant: '{{ product.variation|default('')|e('js') }}',
                        price: {{ product.calculatedPrice.unitPrice }},
                        quantity: 1
                    }]
                });
            }
        })();
    </script>
{% endblock %}

Add to Cart (add_to_cart)

Track when products are added to cart:

{% block page_product_detail_buy_form %}
    {{ parent() }}

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const buyForm = document.querySelector('.product-detail-buy');

            if (buyForm) {
                buyForm.addEventListener('submit', function(e) {
                    const quantity = parseInt(this.querySelector('[name="quantity"]')?.value || 1);
                    {% set product = page.product %}

                    if (typeof gtag !== 'undefined') {
                        gtag('event', 'add_to_cart', {
                            currency: '{{ context.currency.isoCode }}',
                            value: {{ product.calculatedPrice.unitPrice }} * quantity,
                            items: [{
                                item_id: '{{ product.productNumber }}',
                                item_name: '{{ product.translated.name|e('js') }}',
                                item_brand: '{{ product.manufacturer.translated.name|default('')|e('js') }}',
                                item_category: '{{ product.categories.first.translated.name|default('')|e('js') }}',
                                price: {{ product.calculatedPrice.unitPrice }},
                                quantity: quantity
                            }]
                        });
                    }
                });
            }
        });
    </script>
{% endblock %}

AJAX Add to Cart Tracking

For AJAX-based add to cart (like off-canvas cart):

// Listen for Shopware's cart update event
document.$emitter.subscribe('addToCart', function(event) {
    const product = event.detail.product;
    const quantity = event.detail.quantity || 1;

    if (typeof gtag !== 'undefined') {
        gtag('event', 'add_to_cart', {
            currency: product.currency,
            value: product.price * quantity,
            items: [{
                item_id: product.productNumber,
                item_name: product.name,
                price: product.price,
                quantity: quantity
            }]
        });
    }
});

Remove from Cart (remove_from_cart)

Track cart item removals:

{% block page_checkout_cart_item %}
    {{ parent() }}

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const removeButtons = document.querySelectorAll('[data-cart-item-remove]');

            removeButtons.forEach(function(button) {
                button.addEventListener('click', function() {
                    const lineItem = button.closest('[data-cart-item]');
                    const lineItemId = lineItem?.dataset.cartItemId;

                    {% for lineItem in page.cart.lineItems %}
                        if (lineItemId === '{{ lineItem.id }}') {
                            if (typeof gtag !== 'undefined') {
                                gtag('event', 'remove_from_cart', {
                                    currency: '{{ context.currency.isoCode }}',
                                    value: {{ lineItem.price.unitPrice }},
                                    items: [{
                                        item_id: '{{ lineItem.referencedId }}',
                                        item_name: '{{ lineItem.label|e('js') }}',
                                        price: {{ lineItem.price.unitPrice }},
                                        quantity: {{ lineItem.quantity }}
                                    }]
                                });
                            }
                        }
                    {% endfor %}
                });
            });
        });
    </script>
{% endblock %}

View Cart (view_cart)

Track when users view their cart:

{% block page_checkout_cart %}
    {{ parent() }}

    <script>
        (function() {
            {% if page.cart.lineItems.count > 0 %}
                const items = [
                    {% for lineItem in page.cart.lineItems %}
                    {
                        item_id: '{{ lineItem.referencedId }}',
                        item_name: '{{ lineItem.label|e('js') }}',
                        price: {{ lineItem.price.unitPrice }},
                        quantity: {{ lineItem.quantity }}
                    }{% if not loop.last %},{% endif %}
                    {% endfor %}
                ];

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'view_cart', {
                        currency: '{{ context.currency.isoCode }}',
                        value: {{ page.cart.price.totalPrice }},
                        items: items
                    });
                }
            {% endif %}
        })();
    </script>
{% endblock %}

Begin Checkout (begin_checkout)

Track when checkout starts:

{% block page_checkout_confirm %}
    {{ parent() }}

    <script>
        (function() {
            {% if page.cart.lineItems.count > 0 %}
                const items = [
                    {% for lineItem in page.cart.lineItems %}
                    {
                        item_id: '{{ lineItem.referencedId }}',
                        item_name: '{{ lineItem.label|e('js') }}',
                        price: {{ lineItem.price.unitPrice }},
                        quantity: {{ lineItem.quantity }}
                    }{% if not loop.last %},{% endif %}
                    {% endfor %}
                ];

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'begin_checkout', {
                        currency: '{{ context.currency.isoCode }}',
                        value: {{ page.cart.price.totalPrice }},
                        items: items
                    });
                }
            {% endif %}
        })();
    </script>
{% endblock %}

Add Shipping Info (add_shipping_info)

Track when shipping method is selected:

// In checkout JavaScript
document.addEventListener('DOMContentLoaded', function() {
    const shippingOptions = document.querySelectorAll('[name="shippingMethodId"]');

    shippingOptions.forEach(function(option) {
        option.addEventListener('change', function() {
            const shippingMethod = this.dataset.shippingMethodName;

            if (typeof gtag !== 'undefined') {
                gtag('event', 'add_shipping_info', {
                    currency: '{{ context.currency.isoCode }}',
                    value: {{ page.cart.price.totalPrice }},
                    shipping_tier: shippingMethod,
                    items: window.cartItems || []
                });
            }
        });
    });
});

Add Payment Info (add_payment_info)

Track when payment method is selected:

// In checkout JavaScript
document.addEventListener('DOMContentLoaded', function() {
    const paymentOptions = document.querySelectorAll('[name="paymentMethodId"]');

    paymentOptions.forEach(function(option) {
        option.addEventListener('change', function() {
            const paymentMethod = this.dataset.paymentMethodName;

            if (typeof gtag !== 'undefined') {
                gtag('event', 'add_payment_info', {
                    currency: '{{ context.currency.isoCode }}',
                    value: {{ page.cart.price.totalPrice }},
                    payment_type: paymentMethod,
                    items: window.cartItems || []
                });
            }
        });
    });
});

Purchase (purchase)

Track completed orders on the order confirmation page:

{% block page_checkout_finish %}
    {{ parent() }}

    <script>
        (function() {
            {% set order = page.order %}
            {% if order %}
                const items = [
                    {% for lineItem in order.lineItems %}
                    {
                        item_id: '{{ lineItem.productNumber|default(lineItem.id) }}',
                        item_name: '{{ lineItem.label|e('js') }}',
                        price: {{ lineItem.unitPrice }},
                        quantity: {{ lineItem.quantity }}
                    }{% if not loop.last %},{% endif %}
                    {% endfor %}
                ];

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'purchase', {
                        transaction_id: '{{ order.orderNumber }}',
                        value: {{ order.amountTotal }},
                        tax: {{ order.amountTotal - order.amountNet }},
                        shipping: {{ order.shippingCosts.totalPrice }},
                        currency: '{{ order.currency.isoCode }}',
                        coupon: '{{ order.promotions.first.code|default('') }}',
                        items: items
                    });
                }

                // Send to data layer for other tags
                window.dataLayer = window.dataLayer || [];
                window.dataLayer.push({
                    'event': 'purchase',
                    'ecommerce': {
                        'transaction_id': '{{ order.orderNumber }}',
                        'value': {{ order.amountTotal }},
                        'currency': '{{ order.currency.isoCode }}',
                        'items': items
                    }
                });
            {% endif %}
        })();
    </script>
{% endblock %}

Using GTM for Ecommerce Tracking

For easier management, use Google Tag Manager:

1. Push Ecommerce Data to Data Layer

Instead of calling gtag() directly, push to dataLayer:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'event': 'view_item',
    'ecommerce': {
        'currency': 'EUR',
        'value': 29.99,
        'items': [{
            'item_id': 'SKU123',
            'item_name': 'Product Name',
            'price': 29.99,
            'quantity': 1
        }]
    }
});

2. Create Ecommerce Tags in GTM

  • Trigger: Custom Event (event equals view_item)
  • Tag Type: Google Analytics: GA4 Event
  • Event Name: \{\{Event\}\} variable
  • Ecommerce Data: Pull from data layer

See GTM Data Layer Structure for details.

Enhanced Ecommerce Features

Product Variants Tracking

Track product variants (size, color, etc.):

{
    item_id: '{{ product.productNumber }}',
    item_name: '{{ product.translated.name }}',
    item_variant: '{{ product.options|map(o => o.translated.name)|join(", ") }}'
}

Promotion Tracking

Track promotion impressions and clicks:

// Promotion impression
gtag('event', 'view_promotion', {
    'creative_name': 'Summer Sale Banner',
    'creative_slot': 'homepage_hero',
    'promotion_id': 'SUMMER2024',
    'promotion_name': 'Summer Sale',
    'items': []
});

// Promotion click
gtag('event', 'select_promotion', {
    'creative_name': 'Summer Sale Banner',
    'creative_slot': 'homepage_hero',
    'promotion_id': 'SUMMER2024',
    'promotion_name': 'Summer Sale'
});

Refund Tracking

Track refunds (typically done server-side or manually):

gtag('event', 'refund', {
    'transaction_id': 'ORDER123',
    'value': 99.99,
    'currency': 'EUR',
    'items': [{
        'item_id': 'SKU123',
        'item_name': 'Product Name',
        'price': 99.99,
        'quantity': 1
    }]
});

Data Accuracy Best Practices

1. Use Consistent Product IDs

Choose one product identifier and stick with it:

  • Product Number (productNumber) - Recommended
  • SKU - If different from product number
  • Product ID - Database ID (less portable)

2. Include All Required Parameters

Always include:

  • currency - ISO currency code
  • value - Total transaction value
  • items array with at least:
    • item_id
    • item_name
    • price
    • quantity

3. Match GA4 Currency Settings

Ensure currency in events matches GA4 property settings.

4. Test Thoroughly

Test all events:

  • Use GA4 DebugView
  • Check all parameters are correct
  • Verify values match order totals
  • Test on different devices

Troubleshooting

Events Not Firing

Check:

  • GA4 is installed and configured
  • JavaScript console for errors
  • Cookie consent is granted
  • Shopware cache is cleared

See Events Not Firing

Incorrect Values

Common Issues:

  • Currency mismatch
  • VAT/tax included vs. excluded
  • Shipping costs not included
  • Discounts not calculated correctly

Fix:

{# Use Shopware's calculated prices #}
value: {{ order.amountTotal }}  {# Total including tax #}
tax: {{ order.amountTotal - order.amountNet }}

Duplicate Purchase Events

Causes:

  • User refreshing confirmation page
  • Multiple tracking implementations
  • Plugin + manual code both active

Prevention:

// Track purchase only once
if (!sessionStorage.getItem('order_{{ order.orderNumber }}_tracked')) {
    gtag('event', 'purchase', { /* ... */ });
    sessionStorage.setItem('order_{{ order.orderNumber }}_tracked', 'true');
}

Missing Items Data

Issue: Items array is empty or incomplete.

Solution:

  • Verify loop syntax in Twig
  • Check product data is available
  • Add fallback values for optional fields
item_category: '{{ product.categories.first.translated.name|default('Uncategorized') }}'

Performance Optimization

Lazy Load Tracking Scripts

Don't block page rendering:

// Load tracking after page load
window.addEventListener('load', function() {
    // Initialize ecommerce tracking
});

Debounce Frequent Events

For events that fire multiple times:

let addToCartTimeout;
document.addEventListener('cartUpdated', function() {
    clearTimeout(addToCartTimeout);
    addToCartTimeout = setTimeout(function() {
        // Send add_to_cart event
    }, 500);
});

Next Steps

For general GA4 ecommerce concepts, see Google Analytics 4 Guide.