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:
Go to Variables → New
Variable Configuration → Data Layer Variable
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\}\}
- Fields to Set:
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
- Open GTM → Preview
- Connect to your Shopware store
- Navigate pages
- View Data Layer tab in Tag Assistant
- 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:
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
- GA4 Ecommerce Tracking - Use data layer for GA4
- Troubleshoot Tracking - Debug data layer issues
- GTM Setup - Install GTM if not already done
For general GTM and data layer concepts, see Google Tag Manager Documentation.