Shopware Data Layer Setup for GTM and GA4 | OpsBlu Docs

Shopware Data Layer Setup for GTM and GA4

Understanding and implementing Shopware 6 data layer structure for Google Tag Manager integration.

This guide covers how to implement and use a data layer structure for Shopware 6 that works seamlessly with Google Tag Manager (GTM).

Prerequisites: GTM must be installed. See Install Google Tag Manager

What is a Data Layer?

The data layer is a JavaScript object that holds data about the page, user, products, and events. GTM reads from this data layer to populate tags with dynamic values.

Example data layer:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'event': 'page_view',
    'page_type': 'product',
    'product_id': '12345',
    'product_name': 'Running Shoes',
    'price': 79.99,
    'currency': 'EUR'
});

Basic Data Layer Implementation

Initialize Data Layer

Add to your theme's base.html.twig before GTM code:

{% block base_script_tracking %}
    <script>
        // Initialize data layer
        window.dataLayer = window.dataLayer || [];

        // Push page-level data
        window.dataLayer.push({
            'pageType': '{{ page.pageType|default('other') }}',
            'salesChannelId': '{{ context.salesChannel.id }}',
            'salesChannelName': '{{ context.salesChannel.name }}',
            'currency': '{{ context.currency.isoCode }}',
            'language': '{{ context.salesChannel.language.locale.code }}',
            'country': '{{ context.salesChannel.country.iso|default('') }}',
            {% if context.customer %}
            'customerId': '{{ context.customer.id }}',
            'customerGroup': '{{ context.customer.group.translated.name }}',
            'customerType': 'customer',
            {% else %}
            'customerId': '',
            'customerGroup': 'Guest',
            'customerType': 'guest',
            {% endif %}
        });
    </script>

    {{ parent() }}
    {# GTM code here #}
{% endblock %}

Page-Specific Data Layers

Homepage Data Layer

{% block page_content %}
    {% if page.pageType == 'index' %}
        <script>
            window.dataLayer = window.dataLayer || [];
            window.dataLayer.push({
                'event': 'page_view',
                'pageType': 'home',
                'pagePath': '{{ app.request.pathInfo }}',
                'pageTitle': '{{ page.metaInformation.metaTitle|default('Home') }}'
            });
        </script>
    {% endif %}

    {{ parent() }}
{% endblock %}

Product Listing (Category) Data Layer

{% block page_product_listing %}
    {{ parent() }}

    <script>
        window.dataLayer = window.dataLayer || [];

        // Push category data
        window.dataLayer.push({
            'event': 'page_view',
            'pageType': 'category',
            'categoryId': '{{ page.category.id }}',
            'categoryName': '{{ page.category.translated.name|e('js') }}',
            'categoryPath': '{{ page.category.breadcrumb|join(' > ')|e('js') }}',
            'productCount': {{ page.listing.total }}
        });

        // Push product impressions
        {% if page.listing.elements %}
            window.dataLayer.push({
                'event': 'view_item_list',
                'ecommerce': {
                    'item_list_id': '{{ page.category.id }}',
                    'item_list_name': '{{ page.category.translated.name|e('js') }}',
                    '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 }},
                            'quantity': 1,
                            'index': {{ loop.index0 }}
                        }{% if not loop.last %},{% endif %}
                        {% endfor %}
                    ]
                }
            });
        {% endif %}
    </script>
{% endblock %}

Product Detail Page Data Layer

{% block page_product_detail %}
    {{ parent() }}

    <script>
        window.dataLayer = window.dataLayer || [];

        {% set product = page.product %}

        // Push product detail data
        window.dataLayer.push({
            'event': 'page_view',
            'pageType': 'product',
            'productId': '{{ product.id }}',
            'productNumber': '{{ product.productNumber }}',
            'productName': '{{ product.translated.name|e('js') }}',
            'productPrice': {{ product.calculatedPrice.unitPrice }},
            'productCurrency': '{{ context.currency.isoCode }}',
            'productBrand': '{{ product.manufacturer.translated.name|default('')|e('js') }}',
            'productCategory': '{{ product.categories.first.translated.name|default('')|e('js') }}',
            'productStock': {{ product.availableStock }},
            'productAvailable': {{ product.available ? 'true' : 'false' }}
        });

        // Push view_item event
        window.dataLayer.push({
            'event': 'view_item',
            'ecommerce': {
                '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') }}',
                    {% if product.options %}
                    'item_variant': '{{ product.options|map(o => o.translated.name)|join(', ')|e('js') }}',
                    {% endif %}
                    'price': {{ product.calculatedPrice.unitPrice }},
                    'quantity': 1
                }]
            }
        });

        // Store product data for add to cart event
        window.shopwareProduct = {
            id: '{{ product.id }}',
            productNumber: '{{ product.productNumber }}',
            name: '{{ product.translated.name|e('js') }}',
            price: {{ product.calculatedPrice.unitPrice }},
            currency: '{{ context.currency.isoCode }}',
            brand: '{{ product.manufacturer.translated.name|default('')|e('js') }}',
            category: '{{ product.categories.first.translated.name|default('')|e('js') }}'
        };
    </script>
{% endblock %}

Cart Page Data Layer

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

    <script>
        window.dataLayer = window.dataLayer || [];

        // Push cart page view
        window.dataLayer.push({
            'event': 'page_view',
            'pageType': 'cart',
            'cartTotal': {{ page.cart.price.totalPrice }},
            'cartItemCount': {{ page.cart.lineItems.count }},
            'cartCurrency': '{{ context.currency.isoCode }}'
        });

        {% if page.cart.lineItems.count > 0 %}
            // Push view_cart event
            window.dataLayer.push({
                'event': 'view_cart',
                'ecommerce': {
                    'currency': '{{ context.currency.isoCode }}',
                    'value': {{ page.cart.price.totalPrice }},
                    '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 %}
                    ]
                }
            });

            // Store cart data for global access
            window.shopwareCart = {
                items: [
                    {% for lineItem in page.cart.lineItems %}
                    {
                        id: '{{ lineItem.referencedId }}',
                        name: '{{ lineItem.label|e('js') }}',
                        price: {{ lineItem.price.unitPrice }},
                        quantity: {{ lineItem.quantity }}
                    }{% if not loop.last %},{% endif %}
                    {% endfor %}
                ],
                total: {{ page.cart.price.totalPrice }},
                currency: '{{ context.currency.isoCode }}'
            };
        {% endif %}
    </script>
{% endblock %}

Checkout Page Data Layer

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

    <script>
        window.dataLayer = window.dataLayer || [];

        // Push checkout page view
        window.dataLayer.push({
            'event': 'page_view',
            'pageType': 'checkout'
        });

        {% if page.cart.lineItems.count > 0 %}
            // Push begin_checkout event
            window.dataLayer.push({
                'event': 'begin_checkout',
                'ecommerce': {
                    'currency': '{{ context.currency.isoCode }}',
                    'value': {{ page.cart.price.totalPrice }},
                    '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 %}
                    ]
                }
            });
        {% endif %}
    </script>
{% endblock %}

Order Confirmation Data Layer

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

    <script>
        window.dataLayer = window.dataLayer || [];

        {% set order = page.order %}
        {% if order %}
            // Push order confirmation page view
            window.dataLayer.push({
                'event': 'page_view',
                'pageType': 'confirmation',
                'orderId': '{{ order.id }}',
                'orderNumber': '{{ order.orderNumber }}'
            });

            // Push purchase event
            window.dataLayer.push({
                'event': 'purchase',
                'ecommerce': {
                    'transaction_id': '{{ order.orderNumber }}',
                    'affiliation': '{{ context.salesChannel.name }}',
                    'value': {{ order.amountTotal }},
                    'tax': {{ order.amountTotal - order.amountNet }},
                    'shipping': {{ order.shippingCosts.totalPrice }},
                    'currency': '{{ order.currency.isoCode }}',
                    {% if order.lineItems|filter(item => item.type == 'promotion')|length > 0 %}
                    'coupon': '{{ order.lineItems|filter(item => item.type == 'promotion')|first.payload.code }}',
                    {% endif %}
                    'items': [
                        {% for lineItem in order.lineItems %}
                        {% if lineItem.type == 'product' %}
                        {
                            'item_id': '{{ lineItem.payload.productNumber|default(lineItem.id) }}',
                            'item_name': '{{ lineItem.label|e('js') }}',
                            'price': {{ lineItem.unitPrice }},
                            'quantity': {{ lineItem.quantity }}
                        }{% if not loop.last %},{% endif %}
                        {% endif %}
                        {% endfor %}
                    ]
                }
            });
        {% endif %}
    </script>
{% endblock %}

Event-Based Data Layer Pushes

Add to Cart Event

// Listen for Shopware add to cart events
document.addEventListener('DOMContentLoaded', function() {
    // For standard add to cart
    const buyForms = document.querySelectorAll('.product-detail-buy');

    buyForms.forEach(function(form) {
        form.addEventListener('submit', function(e) {
            const quantity = parseInt(this.querySelector('[name="quantity"]')?.value || 1);

            if (window.shopwareProduct) {
                window.dataLayer = window.dataLayer || [];
                window.dataLayer.push({
                    'event': 'add_to_cart',
                    'ecommerce': {
                        'currency': window.shopwareProduct.currency,
                        'value': window.shopwareProduct.price * quantity,
                        'items': [{
                            'item_id': window.shopwareProduct.productNumber,
                            'item_name': window.shopwareProduct.name,
                            'item_brand': window.shopwareProduct.brand,
                            'item_category': window.shopwareProduct.category,
                            'price': window.shopwareProduct.price,
                            'quantity': quantity
                        }]
                    }
                });
            }
        });
    });
});

Remove from Cart Event

// Track cart item removals
document.addEventListener('click', function(e) {
    const removeButton = e.target.closest('[data-cart-item-remove]');

    if (removeButton) {
        const lineItem = removeButton.closest('[data-cart-item]');
        const lineItemId = lineItem?.dataset.cartItemId;

        // Find item in cart data
        if (window.shopwareCart && window.shopwareCart.items) {
            const item = window.shopwareCart.items.find(i => i.id === lineItemId);

            if (item) {
                window.dataLayer = window.dataLayer || [];
                window.dataLayer.push({
                    'event': 'remove_from_cart',
                    'ecommerce': {
                        'currency': window.shopwareCart.currency,
                        'value': item.price * item.quantity,
                        'items': [{
                            'item_id': item.id,
                            'item_name': item.name,
                            'price': item.price,
                            'quantity': item.quantity
                        }]
                    }
                });
            }
        }
    }
}, false);

Search Event

// Track search queries
document.addEventListener('DOMContentLoaded', function() {
    const searchForm = document.querySelector('.header-search-form');

    if (searchForm) {
        searchForm.addEventListener('submit', function(e) {
            const searchInput = searchForm.querySelector('input[name="search"]');
            const searchTerm = searchInput ? searchInput.value : '';

            if (searchTerm) {
                window.dataLayer = window.dataLayer || [];
                window.dataLayer.push({
                    'event': 'search',
                    'search_term': searchTerm
                });
            }
        });
    }
});

Creating GTM Variables from Data Layer

In Google Tag Manager:

  1. Go to VariablesNew

  2. Variable ConfigurationData Layer Variable

  3. Configure Variable:

    • Name: Product Name
    • Data Layer Variable Name: productName
    • Default Value: (not set)

Common GTM Variables for Shopware

Create these Data Layer Variables in GTM:

Variable Name Data Layer Variable Name Type
Page Type pageType Data Layer Variable
Product ID productId Data Layer Variable
Product Name productName Data Layer Variable
Product Price productPrice Data Layer Variable
Currency Code currency Data Layer Variable
Customer ID customerId Data Layer Variable
Customer Group customerGroup Data Layer Variable
Sales Channel ID salesChannelId Data Layer Variable
Order Number orderNumber Data Layer Variable
Cart Total cartTotal Data Layer Variable

Using Data Layer Variables in GTM Tags

Example: GA4 Configuration Tag

Tag Configuration:

  • Tag Type: Google Analytics: GA4 Configuration
  • Measurement ID: G-XXXXXXXXXX
  • Configuration Settings:
    • Fields to Set:
      • currency: \{\{Currency Code\}\}
      • user_id: \{\{Customer ID\}\}
    • User Properties:
      • customer_group: \{\{Customer Group\}\}
      • sales_channel: \{\{Sales Channel ID\}\}

Example: Event Tag with Data Layer

Tag Configuration:

  • Tag Type: Google Analytics: GA4 Event
  • Event Name: view_product
  • Event Parameters:
    • product_id: \{\{Product ID\}\}
    • product_name: \{\{Product Name\}\}
    • value: \{\{Product Price\}\}
    • currency: \{\{Currency Code\}\}

Triggering:

  • Trigger Type: Page View
  • Some Page Views
  • Page Type equals product

Debugging Data Layer

Browser Console

Check data layer contents:

// View entire data layer
console.table(window.dataLayer);

// View latest push
console.log(window.dataLayer[window.dataLayer.length - 1]);

// Monitor all pushes
(function() {
    const originalPush = window.dataLayer.push;
    window.dataLayer.push = function() {
        console.log('dataLayer.push:', arguments[0]);
        return originalPush.apply(window.dataLayer, arguments);
    };
})();

GTM Preview Mode

  1. Open GTM → Preview
  2. Connect to your Shopware store
  3. Navigate pages
  4. View Data Layer tab in Tag Assistant
  5. Check all values populate correctly

Common Issues

Data layer is undefined:

{# Always initialize before use #}
<script>
    window.dataLayer = window.dataLayer || [];
</script>

Values are empty:

  • Check Twig variables are available
  • Verify context data exists
  • Add fallback values:
'productName': '{{ product.translated.name|default('Unknown')|e('js') }}'

JavaScript errors:

  • Escape special characters in strings
  • Use |e('js') filter in Twig
  • Check for missing commas in JSON

Advanced Data Layer Patterns

User Login Event

// After successful login
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'event': 'login',
    'method': 'email',
    'userId': '{{ customer.id }}',
    'customerGroup': '{{ customer.group.translated.name }}'
});

Newsletter Signup

// After newsletter form submission
window.dataLayer.push({
    'event': 'newsletter_signup',
    'location': 'footer',
    'email_hash': hashEmail(email) // Hash email for privacy
});

Product Click

// When product is clicked in listing
window.dataLayer.push({
    'event': 'select_item',
    'ecommerce': {
        'item_list_id': 'category_123',
        'item_list_name': 'Running Shoes',
        'items': [{
            'item_id': 'SKU123',
            'item_name': 'Product Name',
            'index': 0
        }]
    }
});

Best Practices

1. Always Initialize Data Layer

<script>
    window.dataLayer = window.dataLayer || [];
</script>

2. Push Events After Data

// Bad: Event might fire before data is available
window.dataLayer.push({ 'event': 'purchase' });
window.dataLayer.push({ 'transactionId': '12345' });

// Good: Data before event
window.dataLayer.push({
    'transactionId': '12345',
    'event': 'purchase'
});

3. Escape User-Generated Content

{# Always escape to prevent XSS #}
'productName': '{{ product.translated.name|e('js') }}'

4. Use Consistent Naming

  • Use camelCase or snake_case consistently
  • Match GA4 parameter names when possible
  • Document custom parameters

5. Don't Push Sensitive Data

Never push:

  • Full email addresses (hash if needed)
  • Passwords
  • Credit card numbers
  • Personal identification numbers

Hash sensitive data:

function hashEmail(email) {
    // Use a hashing library
    return CryptoJS.SHA256(email.toLowerCase()).toString();
}

Next Steps

For general GTM and data layer concepts, see Google Tag Manager Documentation.