GA4 Event Tracking on Shopware | OpsBlu Docs

GA4 Event Tracking on Shopware

Implement custom event tracking for Google Analytics 4 on Shopware 6 using storefront events and data layer.

This guide covers how to track custom events and user interactions in Google Analytics 4 on Shopware 6.

For GA4 installation, see Install Google Analytics 4

Shopware Storefront Event System

Shopware 6 provides a built-in event system for the storefront that you can leverage for tracking.

Available Storefront Events

Shopware's JavaScript event system uses a publish/subscribe pattern:

// Subscribe to events
document.$emitter.subscribe('eventName', function(event) {
    // Handle event
});

// Publish events
document.$emitter.publish('eventName', { data: 'value' });

Common Shopware Events

Event Trigger Data Available
offcanvas-cart-opened Cart drawer opened Cart data
offcanvas-cart-closed Cart drawer closed -
ajax-offcanvas-cart-finished Cart updated via AJAX Cart contents
Listing/afterRenderFilters Product filters applied Filter values
ProductDetail/afterRenderWishlist Product added to wishlist Product data

Tracking Standard GA4 Events

Page View Tracking

Page views are automatically tracked if configured in GA4 setup:

gtag('config', 'G-XXXXXXXXXX', {
    'send_page_view': true
});

For single-page app behavior or AJAX navigation:

// Track virtual page views
gtag('event', 'page_view', {
    'page_title': document.title,
    'page_location': window.location.href,
    'page_path': window.location.pathname
});

Search Tracking

Track search queries when users search your store:

{% block base_header_search_suggest %}
    {{ parent() }}

    <script>
        // Listen for search form submission
        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 && typeof gtag !== 'undefined') {
                        gtag('event', 'search', {
                            'search_term': searchTerm
                        });
                    }
                });
            }
        });
    </script>
{% endblock %}

Filter Usage Tracking

Track when users apply product filters:

// Custom JavaScript plugin or theme.js
document.$emitter.subscribe('Listing/afterRenderFilters', function(event) {
    const filters = event.detail;

    if (typeof gtag !== 'undefined') {
        gtag('event', 'filter_products', {
            'filter_type': filters.type || 'unknown',
            'filter_value': filters.value || '',
            'category': filters.category || 'all'
        });
    }
});

Wishlist Tracking

Track when products are added to wishlist:

{% block page_product_detail_wishlist %}
    {{ parent() }}

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const wishlistButton = document.querySelector('[data-wishlist-add-button]');

            if (wishlistButton) {
                wishlistButton.addEventListener('click', function() {
                    const productId = this.dataset.productId;
                    const productName = this.dataset.productName || 'Unknown';

                    if (typeof gtag !== 'undefined') {
                        gtag('event', 'add_to_wishlist', {
                            'items': [{
                                'item_id': productId,
                                'item_name': productName
                            }]
                        });
                    }
                });
            }
        });
    </script>
{% endblock %}

Newsletter Signup Tracking

Track newsletter subscriptions:

{% block footer_newsletter_form %}
    {{ parent() %}

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const newsletterForm = document.querySelector('.footer-newsletter-form');

            if (newsletterForm) {
                newsletterForm.addEventListener('submit', function(e) {
                    if (typeof gtag !== 'undefined') {
                        gtag('event', 'newsletter_signup', {
                            'method': 'footer_form',
                            'location': 'footer'
                        });
                    }
                });
            }
        });
    </script>
{% endblock %}

Custom Event Tracking

Button Click Tracking

Track specific button clicks:

// Add to custom JavaScript file
document.addEventListener('DOMContentLoaded', function() {
    // Track CTA button clicks
    document.querySelectorAll('[data-track-click]').forEach(function(button) {
        button.addEventListener('click', function() {
            const eventName = this.dataset.trackClick;
            const eventCategory = this.dataset.trackCategory || 'engagement';
            const eventLabel = this.dataset.trackLabel || this.textContent;

            if (typeof gtag !== 'undefined') {
                gtag('event', eventName, {
                    'event_category': eventCategory,
                    'event_label': eventLabel
                });
            }
        });
    });
});

Usage in Twig:

<button
    data-track-click="view_size_guide"
    data-track-category="product_interaction"
    data-track-label="Size Guide Button">
    View Size Guide
</button>

Track outbound links:

// Track external link clicks
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('a[href^="http"]').forEach(function(link) {
        if (!link.href.includes(window.location.hostname)) {
            link.addEventListener('click', function(e) {
                const destination = this.href;

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'click', {
                        'event_category': 'outbound_link',
                        'event_label': destination,
                        'transport_type': 'beacon'
                    });
                }
            });
        }
    });
});

File Download Tracking

Track PDF downloads and other files:

document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('a[href$=".pdf"], a[href$=".zip"], a[href$=".doc"]').forEach(function(link) {
        link.addEventListener('click', function(e) {
            const filePath = this.href;
            const fileName = filePath.split('/').pop();
            const fileType = fileName.split('.').pop();

            if (typeof gtag !== 'undefined') {
                gtag('event', 'file_download', {
                    'file_name': fileName,
                    'file_extension': fileType,
                    'link_url': filePath
                });
            }
        });
    });
});

Video Interaction Tracking

Track video plays (for embedded videos):

// For HTML5 videos
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('video').forEach(function(video) {
        // Track play
        video.addEventListener('play', function() {
            if (typeof gtag !== 'undefined') {
                gtag('event', 'video_start', {
                    'video_title': this.title || 'Unknown',
                    'video_url': this.currentSrc
                });
            }
        });

        // Track completion
        video.addEventListener('ended', function() {
            if (typeof gtag !== 'undefined') {
                gtag('event', 'video_complete', {
                    'video_title': this.title || 'Unknown',
                    'video_url': this.currentSrc
                });
            }
        });

        // Track progress (25%, 50%, 75%)
        const progressPoints = [0.25, 0.5, 0.75];
        const tracked = new Set();

        video.addEventListener('timeupdate', function() {
            const progress = this.currentTime / this.duration;

            progressPoints.forEach(function(point) {
                if (progress >= point && !tracked.has(point)) {
                    tracked.add(point);

                    if (typeof gtag !== 'undefined') {
                        gtag('event', 'video_progress', {
                            'video_title': video.title || 'Unknown',
                            'video_percent': Math.round(point * 100)
                        });
                    }
                }
            });
        });
    });
});

User Engagement Events

Scroll Depth Tracking

Track how far users scroll on pages:

// Scroll depth tracking
(function() {
    const scrollThresholds = [25, 50, 75, 100];
    const trackedThresholds = new Set();

    window.addEventListener('scroll', function() {
        const scrollPercent = (window.scrollY + window.innerHeight) / document.body.scrollHeight * 100;

        scrollThresholds.forEach(function(threshold) {
            if (scrollPercent >= threshold && !trackedThresholds.has(threshold)) {
                trackedThresholds.add(threshold);

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'scroll', {
                        'event_category': 'engagement',
                        'event_label': threshold + '%',
                        'value': threshold
                    });
                }
            }
        });
    });
})();

Time on Page

Track engaged time on page:

// Track engaged time (user is active)
(function() {
    let startTime = Date.now();
    let isActive = true;
    let totalEngagedTime = 0;

    // Track user activity
    ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(function(event) {
        document.addEventListener(event, function() {
            isActive = true;
        }, { passive: true });
    });

    // Check engagement every second
    setInterval(function() {
        if (isActive) {
            totalEngagedTime += 1;
            isActive = false;
        }
    }, 1000);

    // Send engaged time when leaving page
    window.addEventListener('beforeunload', function() {
        if (typeof gtag !== 'undefined' && totalEngagedTime > 0) {
            gtag('event', 'user_engagement', {
                'engagement_time_msec': totalEngagedTime * 1000,
                'page_path': window.location.pathname
            });
        }
    });
})();

Enhanced Measurement Events

Form Interactions

Track form starts and submissions:

// Track form interactions
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('form').forEach(function(form) {
        let formStarted = false;

        // Track form start (first interaction)
        form.addEventListener('focus', function(e) {
            if (!formStarted && e.target.matches('input, textarea, select')) {
                formStarted = true;

                if (typeof gtag !== 'undefined') {
                    gtag('event', 'form_start', {
                        'form_id': form.id || 'unnamed',
                        'form_name': form.name || 'unnamed'
                    });
                }
            }
        }, true);

        // Track form submission
        form.addEventListener('submit', function() {
            if (typeof gtag !== 'undefined') {
                gtag('event', 'form_submit', {
                    'form_id': form.id || 'unnamed',
                    'form_name': form.name || 'unnamed'
                });
            }
        });
    });
});

Account Events

Track account-related actions:

{# In account login template #}
{% block page_account_login_form %}
    {{ parent() }}

    <script>
        document.querySelector('.login-form')?.addEventListener('submit', function() {
            if (typeof gtag !== 'undefined') {
                gtag('event', 'login', {
                    'method': 'email'
                });
            }
        });
    </script>
{% endblock %}

{# In account registration template #}
{% block page_account_register_form %}
    {{ parent() }}

    <script>
        document.querySelector('.register-form')?.addEventListener('submit', function() {
            if (typeof gtag !== 'undefined') {
                gtag('event', 'sign_up', {
                    'method': 'email'
                });
            }
        });
    </script>
{% endblock %}

Using GTM for Event Tracking

For more flexible event tracking, use Google Tag Manager:

1. Set Up GTM

See Install Google Tag Manager

2. Push Events to Data Layer

// Instead of gtag(), push to dataLayer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'event': 'customEvent',
    'eventCategory': 'category',
    'eventAction': 'action',
    'eventLabel': 'label',
    'eventValue': 123
});

3. Create Tags in GTM

  • Trigger: Custom Event (event equals customEvent)
  • Tag: GA4 Event
  • Event Name: From variable \{\{eventAction\}\}
  • Parameters: Map from data layer variables

Event Parameters Best Practices

Standard Parameters:

  • item_id: Product/content identifier
  • item_name: Human-readable name
  • value: Monetary value (for conversions)
  • currency: ISO currency code
  • method: How the action was performed

Custom Parameters:

  • user_type: "customer" or "guest"
  • sales_channel: Sales channel ID
  • customer_group: Customer group name
  • language: Current language
  • device_type: Mobile, tablet, desktop

Parameter Naming Convention

Use snake_case for consistency with GA4:

// Good
gtag('event', 'custom_event', {
    'event_category': 'engagement',
    'product_category': 'shoes'
});

// Avoid
gtag('event', 'customEvent', {
    'EventCategory': 'engagement',
    'ProductCategory': 'shoes'
});

Debugging Event Tracking

Browser Console

Check if events are firing:

// Monitor gtag calls
window.gtag = new Proxy(window.gtag || function(){}, {
    apply: function(target, thisArg, argumentsList) {
        console.log('gtag called:', argumentsList);
        return target.apply(thisArg, argumentsList);
    }
});

GA4 DebugView

  1. Enable debug mode:
gtag('config', 'G-XXXXXXXXXX', {
    'debug_mode': true
});
  1. View in GA4: AdminDebugView

GTM Preview Mode

If using GTM:

  1. Open GTM → Preview
  2. Enter your Shopware store URL
  3. Navigate and trigger events
  4. View events in Tag Assistant

Common Issues

Events not showing:

  • Check cookie consent is granted
  • Verify GA4 Measurement ID is correct
  • Check JavaScript console for errors
  • Clear Shopware cache
  • Verify gtag is defined

Duplicate events:

  • Check for multiple implementations
  • Verify event is only triggered once
  • Use event listeners with { once: true }

Performance Considerations

Debounce High-Frequency Events

For events that fire frequently (scroll, mouse move):

function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// Use debounced scroll
const trackScroll = debounce(function() {
    gtag('event', 'scroll', { /* ... */ });
}, 500);

window.addEventListener('scroll', trackScroll);

Limit Event Volume

Set reasonable limits on tracking:

  • Don't track every mouse movement
  • Limit scroll depth to key thresholds
  • Batch similar events when possible
  • Use sampling for high-traffic sites

Next Steps

For general GA4 event concepts, see Google Analytics 4 Event Tracking.