Server-Side Tracking for Shopware 6
Stop losing conversions to iOS ITP, short-lived cookies, and ad-blockers. TrakSolv hosts a first-party server-side GTM container for your Shopware 6 store and forwards clean, deduplicated conversions to GA4, Meta CAPI, Google Ads and more. Live in minutes, flat monthly pricing.
The pixel alone is leaking your data.
Browser-side tracking on Shopware 6 loses a growing share of conversions. Here's what moving to a server-side setup fixes.
Recover conversions lost to ITP & ad-blockers
On Shopware 6, browser-side pixels are throttled by Safari/Firefox ITP, capped 7-day cookies, and blocked outright by ad-blockers and consent walls. Server-side tracking moves the measurement off the browser, so purchases and add-to-carts that the pixel silently drops are recovered and reported.
First-party data, served from your own domain
TrakSolv hosts your Google Tag Manager server container on a subdomain of your Shopware 6 site. Requests come from a first-party context — they survive 3rd-party cookie deprecation, last far longer than client-side cookies, and look like your own traffic, not a tracker.
Clean, deduplicated conversions to every ad platform
The Shopware 6 dataLayer fires every GA4 enhanced-ecommerce event with a stable event_id, so your server-side container can forward purchases and add-to-carts to GA4, Meta CAPI, Google Ads Enhanced Conversions, TikTok and more — deduplicated against the browser pixel, with higher match quality.
How to set up server-side tracking on Shopware 6
Paste the TrakSolv loader and the Shopware 6 dataLayer snippet into your store's custom-code area, save, and you're done. Every step below has the exact snippet with a copy button.
Install guide
How to install on Shopware 6
This is your install. Every step below has its exact snippet with a Copy button — go step by step. The Loader card above and DataLayer card below are reference copies of the same snippets.
- 1
Open your storefront theme settings
NavigateShopware Admin → Settings → Sales Channels → your storefront → Theme → Customize
Look for the 'Custom JavaScript' / 'Additional Code' field. If your theme doesn't expose one, override Resources/views/storefront/base.html.twig directly.
- 2
Loader — head half
Loader • HeadTheme → Custom HTML/JS (Header) → paste the Loader Head snippet
- 3
Loader — body half + DataLayer
Loader • BodyTheme → Custom HTML/JS (Footer) → paste the Loader Body snippet FIRST
- 4
DataLayer snippet
DataLayerSame Footer field → append the DataLayer snippet right after the Body
The DataLayer subscribes to document.$emitter for addToCart / removeLineItem events — Shopware's native storefront event bus.
HTML<!-- Traksolv · Shopware 6 DataLayer — Theme Custom JS / base.html.twig --> <script> (function (w, d) { w.dataLayer = w.dataLayer || []; var DEBUG = false; try { var q = new URLSearchParams(location.search); if (q.get('tcDebug') === '1') { DEBUG = true; try { localStorage.setItem('tcDebug', '1'); } catch (e) {} } else if (q.get('tcDebug') === '0') { DEBUG = false; try { localStorage.removeItem('tcDebug'); } catch (e) {} } else { try { if (localStorage.getItem('tcDebug') === '1') DEBUG = true; } catch (e) {} } } catch (e) {} w.tcDebug = DEBUG; if (DEBUG && w.console && w.console.log) { try { w.console.log('%c[Traksolv]%c dataLayer debug ON — every push will log here. Disable with ?tcDebug=0', 'color:#22c55e;font-weight:600;background:#0a1f0c;padding:2px 6px;border-radius:3px', ''); } catch (e) {} } function log() { if (!DEBUG || !w.console || !w.console.log) return; try { w.console.log.apply(w.console, ['[Traksolv]'].concat([].slice.call(arguments))); } catch (e) {} } function push(eventName, eventId, params) { try { if (params && params.ecommerce !== undefined) w.dataLayer.push({ ecommerce: null }); var ev = { event: eventName, event_id: eventName + '.' + eventId }; if (params) for (var k in params) if (Object.prototype.hasOwnProperty.call(params, k)) ev[k] = params[k]; w.dataLayer.push(ev); log('push ' + eventName, ev); } catch (e) {} } function once(key) { try { if (sessionStorage.getItem(key)) return false; sessionStorage.setItem(key, '1'); return true; } catch (e) { return true; } } function money(v) { return parseFloat(String(v == null ? '' : v).replace(/[^\d.\-]/g, '')) || 0; } function textOf(sel, root) { var el = (root || d).querySelector(sel); return el ? (el.textContent || '').trim() : ''; } function attrOf(sel, attr, root) { var el = (root || d).querySelector(sel); return el ? (el.getAttribute(attr) || '') : ''; } function currency() { return attrOf('meta[itemprop="priceCurrency"]', 'content') || attrOf('[data-currency-iso]', 'data-currency-iso') || attrOf('[data-currency-symbol]', 'data-currency-symbol') || 'EUR'; } w.tcFirePurchase = function (o) { try { if (!o || !o.orderId) return; if (!once('tc_purchase_' + o.orderId)) return; push('purchase', String(o.orderId), { ecommerce: { transaction_id: String(o.orderId), value: o.value || 0, currency: o.currency || currency(), items: Array.isArray(o.items) ? o.items : [] } }); } catch (e) {} }; // Shopware sets data-route on the body tag for every page. // Real route names: frontend.detail.page, frontend.navigation.page, // frontend.checkout.cart.page, frontend.checkout.confirm.page, // frontend.checkout.finish.page. function pageType() { var route = (d.body && d.body.getAttribute('data-route')) || ''; if (/checkout\.finish/.test(route)) return 'order'; if (/checkout\.confirm/.test(route)) return 'checkout'; if (/checkout\.cart/.test(route)) return 'cart'; if (/detail/.test(route)) return 'product'; if (/navigation|listing|search/.test(route)) return 'category'; return 'other'; } w.tcPageType = pageType; // ── PDP — view_item ── function fireViewItem() { try { var form = d.querySelector('form[data-add-to-cart], .product-detail-buy form'); var pid = (form && form.querySelector('input[name="product-id"], input[name="lineItems[0][referencedId]"], input[name="lineItems[][referencedId]"]')); pid = pid ? pid.value : ''; pid = pid || attrOf('meta[itemprop="productID"]', 'content') || textOf('[itemprop="sku"]') || attrOf('.product-detail-ordernumber-content', 'data-product-id'); if (!pid) return; if (!once('tc_view_item_' + pid)) return; var name = textOf('.product-detail-name, h1[itemprop="name"], h1.product-name'); var price = money(attrOf('meta[itemprop="price"]', 'content') || textOf('.product-detail-price')); var brand = textOf('.product-detail-manufacturer-link, [itemprop="brand"]'); push('view_item', String(pid), { ecommerce: { currency: currency(), value: price, items: [{ item_id: String(pid), item_name: name, item_brand: brand || undefined, price: price, quantity: 1 }] } }); } catch (e) {} } function fireViewItemList() { try { var cards = d.querySelectorAll('.cms-element-product-listing .product-box, .product-box'); if (!cards.length) return; if (!once('tc_view_item_list_' + location.pathname)) return; var items = []; for (var i = 0; i < Math.min(cards.length, 25); i++) { var c = cards[i]; var link = c.querySelector('a.product-name, a[href*="/detail/"], a[href*="/p/"]'); var pid = c.getAttribute('data-product-id') || textOf('[itemprop="sku"]', c) || (link && link.href) || ''; if (!pid) continue; items.push({ item_id: String(pid).slice(-64), item_name: textOf('.product-name, h3, [itemprop="name"], a.product-name', c), price: money(attrOf('meta[itemprop="price"]', 'content', c) || textOf('.product-price', c)), index: i }); } if (!items.length) return; push('view_item_list', location.pathname, { ecommerce: { item_list_id: location.pathname, item_list_name: textOf('h1') || location.pathname, items: items } }); } catch (e) {} } function readLineItems(rowSel) { var rows = d.querySelectorAll(rowSel); var items = []; var total = 0; for (var i = 0; i < rows.length; i++) { var r = rows[i]; var name = textOf('.line-item-label, .line-item-product-name, .checkout-product-name, a', r); var price = money(textOf('.line-item-total-price, .line-item-price, .checkout-product-total', r)); var qty = parseInt((textOf('.line-item-quantity-container input, .line-item-quantity', r) || attrOf('input[name*="quantity"]', 'value', r) || '1') .replace(/\D/g, '') || '1', 10) || 1; var pid = r.getAttribute('data-product-id') || attrOf('input[name*="referencedId"]', 'value', r) || name; items.push({ item_id: String(pid), item_name: name, price: price, quantity: qty }); total += price * qty; } return { items: items, total: total }; } function fireCart(eventName) { try { var data = readLineItems('.line-item, .checkout-product, .cart-item'); if (!data.items.length) return; var key = location.pathname + '.' + data.items.length; if (!once('tc_' + eventName + '_' + key)) return; push(eventName, key.slice(0, 64), { ecommerce: { currency: currency(), value: data.total, items: data.items } }); } catch (e) {} } function firePurchase() { try { var orderId = textOf('.finish-ordernumber strong') || textOf('.finish-ordernumber') || attrOf('[data-order-id]', 'data-order-id') || attrOf('input[name="orderId"]', 'value') || ''; if (!orderId) { var ld = d.querySelector('script[type="application/ld+json"]'); if (ld) try { var p = JSON.parse(ld.textContent); if (p && p.orderNumber) orderId = p.orderNumber; } catch (e) {} } // Strip "Order number: " label prefix some themes ship. orderId = orderId.replace(/^[^0-9A-Za-z]+/, ''); orderId = orderId || ('order.' + Date.now()); if (!once('tc_purchase_' + orderId)) return; var data = readLineItems('.checkout-product, .finish-product-table .line-item, .finish-product, .line-item'); var total = money(textOf('.finish-total-amount, .checkout-total-amount, [data-order-total]')); if (!total) total = data.total; push('purchase', String(orderId), { ecommerce: { transaction_id: String(orderId), value: total, currency: currency(), items: data.items } }); } catch (e) {} } // ── Hook Shopware plugin emitters (per-plugin pattern) ── // For each AddToCart form on the page, subscribe to its plugin // instance's emitter. AddToCart is initialised on form[data-add-to-cart]. function hookAddToCartPlugins() { try { if (!w.PluginManager || typeof w.PluginManager.getPluginInstanceFromElement !== 'function') return false; var forms = d.querySelectorAll('form[data-add-to-cart]'); if (!forms.length) return false; for (var i = 0; i < forms.length; i++) { (function (form) { try { var inst = w.PluginManager.getPluginInstanceFromElement(form, 'AddToCart'); if (!inst || !inst.$emitter || typeof inst.$emitter.subscribe !== 'function') return; inst.$emitter.subscribe('beforeFormSubmit', function (ev) { try { var formData = (ev && ev.detail && ev.detail.formData) || null; var pid = (formData && (formData.get('lineItems[' + (formData.get('lineItems[][referencedId]') ? '' : '0') + '][referencedId]') || formData.get('lineItems[][referencedId]'))) || (form.querySelector('input[name*="referencedId"]') || {}).value || ''; var qty = parseInt((form.querySelector('input[name*="quantity"]') || {}).value || '1', 10) || 1; var name = textOf('.product-detail-name, h1[itemprop="name"]') || textOf('.product-name', form.closest('.product-box')) || textOf('.product-name', form) || ''; var price = money(attrOf('meta[itemprop="price"]', 'content') || textOf('.product-detail-price') || textOf('.product-price', form.closest('.product-box'))); push('add_to_cart', String(pid || 'item') + '.' + Date.now(), { ecommerce: { currency: currency(), value: price * qty, items: [{ item_id: String(pid || name || 'item'), item_name: name, price: price, quantity: qty }] } }); } catch (e) {} }); } catch (e) {} })(forms[i]); } return true; } catch (e) { return false; } } // OffCanvasCart plugin lives on .header-cart-btn (and any data-offcanvas-cart anchor). function hookOffCanvasCart() { try { if (!w.PluginManager || typeof w.PluginManager.getPluginInstanceFromElement !== 'function') return false; var btn = d.querySelector('[data-offcanvas-cart], .header-cart-btn'); if (!btn) return false; var inst = w.PluginManager.getPluginInstanceFromElement(btn, 'OffCanvasCart'); if (!inst || !inst.$emitter || typeof inst.$emitter.subscribe !== 'function') return false; inst.$emitter.subscribe('onRemoveProductFromCart', function (ev) { try { // The event target is the remove-button inside the offcanvas line-item row. var btn = (ev && ev.detail && ev.detail.event && ev.detail.event.target) || null; var row = btn && btn.closest && btn.closest('.line-item, .cart-item, .js-pseudo-modal .line-item'); var name = row ? textOf('.line-item-label, .line-item-product-name, a', row) : ''; var price = row ? money(textOf('.line-item-total-price, .line-item-price', row)) : 0; var qty = 1; var pid = (row && (row.getAttribute('data-product-id') || attrOf('input[name*="referencedId"]', 'value', row))) || name || 'item'; push('remove_from_cart', String(pid) + '.' + Date.now(), { ecommerce: { currency: currency(), value: price * qty, items: [{ item_id: String(pid), item_name: name, price: price, quantity: qty }] } }); } catch (e) {} }); inst.$emitter.subscribe('offCanvasOpened', function () { // Treat each offcanvas-open as a view_cart impression. try { var rows = d.querySelectorAll('.offcanvas .line-item, .offcanvas-body .line-item'); if (!rows.length) return; var items = []; var total = 0; for (var i = 0; i < rows.length; i++) { var r = rows[i]; var name = textOf('.line-item-label, a', r); var price = money(textOf('.line-item-total-price, .line-item-price', r)); var qty = parseInt((textOf('.line-item-quantity', r) || '1').replace(/\D/g, '') || '1', 10) || 1; var pid = r.getAttribute('data-product-id') || attrOf('input[name*="referencedId"]', 'value', r) || name; items.push({ item_id: String(pid), item_name: name, price: price, quantity: qty }); total += price * qty; } push('view_cart', 'offcanvas.' + Date.now(), { ecommerce: { currency: currency(), value: total, items: items } }); } catch (e) {} }); return true; } catch (e) { return false; } } function attachEmitters() { var attempts = 0; var iv = setInterval(function () { var ok1 = hookAddToCartPlugins(); var ok2 = hookOffCanvasCart(); if ((ok1 && ok2) || ++attempts > 40) clearInterval(iv); }, 250); } function boot() { var t = pageType(); if (t === 'product') fireViewItem(); if (t === 'category') fireViewItemList(); if (t === 'cart') fireCart('view_cart'); if (t === 'checkout') fireCart('begin_checkout'); if (t === 'order') firePurchase(); attachEmitters(); } if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', boot); else boot(); })(window, document); </script> <!-- End Traksolv Shopware DataLayer --> <!-- Traksolv · DataLayer enrichment — customer lifecycle + remarketing + attribution --> <script> (function (w, d) { function readLT() { try { var raw = localStorage.getItem('tc_lifetime'); var p = raw ? JSON.parse(raw) : null; return { orderCount: (p && p.orderCount) || 0, lifetimeValue: (p && p.lifetimeValue) || 0, firstOrderTs: (p && p.firstOrderTs) || null, lastOrderTs: (p && p.lastOrderTs) || null, lastOrderId: (p && p.lastOrderId) || null }; } catch (e) { return { orderCount: 0, lifetimeValue: 0, firstOrderTs: null, lastOrderTs: null, lastOrderId: null }; } } function writeLT(lt) { try { localStorage.setItem('tc_lifetime', JSON.stringify(lt)); } catch (e) {} } function tier(v) { if (v >= 1000) return 'vip'; if (v >= 250) return 'mid'; if (v > 0) return 'low'; return 'guest'; } function readCookie(n) { try { var m = d.cookie.match(new RegExp('(^|; )' + n + '=([^;]+)')); return m ? decodeURIComponent(m[2]) : null; } catch (e) { return null; } } function readAttr() { try { var r = readCookie('_tc_attr'); return r ? JSON.parse(r) : null; } catch (e) { return null; } } w.tcReadLifetime = readLT; w.tcCustomerTier = function (v) { return tier(v != null ? v : readLT().lifetimeValue); }; w.tcReadAttribution = readAttr; // ── Wrap tcFirePurchase to add customer-lifecycle + attribution ── // before the underlying snippet pushes the purchase event. Idempotent // per orderId, so customer refreshing the thank-you page doesn't // inflate their lifetime counters. var orig = w.tcFirePurchase; if (typeof orig === 'function') { w.tcFirePurchase = function (o) { try { if (!o || !o.orderId) return orig.apply(this, arguments); var lt = readLT(); var firstFire = lt.lastOrderId !== String(o.orderId); var orderCount = firstFire ? lt.orderCount + 1 : lt.orderCount; var lifetimeValue = firstFire ? lt.lifetimeValue + (o.value || 0) : lt.lifetimeValue; var firstOrderTs = lt.firstOrderTs || Date.now(); var newCustomer = orderCount === 1; var daysSinceFirst = Math.floor((Date.now() - firstOrderTs) / 86400000); if (firstFire) { writeLT({ orderCount: orderCount, lifetimeValue: lifetimeValue, firstOrderTs: firstOrderTs, lastOrderTs: Date.now(), lastOrderId: String(o.orderId) }); } var attr = readAttr(); // Build the enriched order. Preserve every original field; add // customer + attribution on top. var enriched = {}; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) enriched[k] = o[k]; enriched.new_customer = newCustomer; enriched.customer_type = newCustomer ? 'new' : 'returning'; enriched.customer_status = 'customer'; enriched.customer_lifetime_value = Math.round(lifetimeValue * 100) / 100; enriched.customer_order_count = orderCount; enriched.customer_aov = orderCount > 0 ? Math.round((lifetimeValue / orderCount) * 100) / 100 : 0; enriched.purchase_number = orderCount; enriched.days_since_first_order = daysSinceFirst; enriched.value_tier = tier(lifetimeValue); if (attr) enriched.attribution = { landing: attr.u || null, referrer: attr.r || null, utm: attr.utm || null, click_ids: attr.cid || null }; return orig.call(this, enriched); } catch (e) { try { return orig.apply(this, arguments); } catch (e2) {} } }; } // ── tc_visitor_context — emits visitor lifecycle + remarketing on // every page-view. GTM / GA4 audience triggers + Google Ads // dynamic remarketing tag read these. function visitorContext() { try { var lt = readLT(); var attr = readAttr(); var pageType = (typeof w.tcPageType === 'function') ? (w.tcPageType() || 'other') : 'other'; w.dataLayer = w.dataLayer || []; w.dataLayer.push({ event: 'tc_visitor_context', event_id: 'tc_visitor_context.' + Date.now() + '.' + Math.random().toString(36).slice(2, 6), customer_status: lt.orderCount > 0 ? 'returning_customer' : 'guest', customer_type: lt.orderCount > 0 ? 'returning' : 'guest', value_tier: tier(lt.lifetimeValue), customer_lifetime_value: Math.round(lt.lifetimeValue * 100) / 100, customer_order_count: lt.orderCount, days_since_first_order: lt.firstOrderTs ? Math.floor((Date.now() - lt.firstOrderTs) / 86400000) : null, ecomm_pagetype: pageType, attribution: attr ? { landing: attr.u || null, referrer: attr.r || null, utm: attr.utm || null, click_ids: attr.cid || null } : null }); } catch (e) {} } if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', visitorContext); else visitorContext(); })(window, document); </script> <!-- End Traksolv enrichment --> - 5
Save and rebuild the storefront theme
SaveSave → Theme → Save (rebuilds the storefront) → reload the storefront
Shopware compiles the theme on save. Rebuilding takes 10-30s; refresh after.
The loader Head + Body snippets are generated for your container inside the TrakSolv dashboard. Create a free account to grab them, or see the full how-it-works walkthrough.
Server-side tracking for Shopware 6, answered
Does server-side tracking work with Shopware 6?
Yes. TrakSolv works with Shopware 6 via a lightweight loader snippet plus a Shopware 6-specific dataLayer that fires every GA4 enhanced-ecommerce event. You paste it once in Shopware 6's custom-code area and conversions start flowing through your own server-side GTM container.
How do I install TrakSolv on Shopware 6?
Add the TrakSolv loader (head + body) and the Shopware 6 dataLayer snippet to your store's custom-code settings, then save. The full step-by-step is on this page. Most Shopware 6 merchants are live in a few minutes — no developer required.
What can I track server-side on Shopware 6?
Page views, product views, purchases and add-to-carts, begin-checkout and more — every standard GA4 enhanced-ecommerce event, forwarded server-side to GA4, Meta CAPI, Google Ads and other destinations with deduplication.
How much does server-side tracking for Shopware 6 cost?
TrakSolv uses flat monthly pricing — one event sent to many destinations still counts as one event, and paid plans never auto-pause at quota. You can start free and go live in minutes.
Recover your Shopware 6 conversions — start free.
Spin up a first-party server-side GTM container for Shopware 6 and watch your match quality climb. No credit card, live in minutes.
