Installing Google Analytics 4 on TYPO3 | OpsBlu Docs

Installing Google Analytics 4 on TYPO3

Complete guide to implementing GA4 on TYPO3 using extensions, TypoScript, and Fluid templates

This guide covers TYPO3-specific methods for implementing Google Analytics 4 (GA4), from beginner-friendly extensions to enterprise-grade custom implementations using TypoScript and Fluid.

Prerequisites

Before installing GA4 on TYPO3:

  1. Create a GA4 Property in Google Analytics

    • Sign in to analytics.google.com
    • Create a new GA4 property
    • Copy your Measurement ID (format: G-XXXXXXXXXX)
  2. Verify TYPO3 Version Compatibility

    • TYPO3 11 LTS (PHP 7.4+)
    • TYPO3 12 LTS (PHP 8.1+)
    • TYPO3 13 (PHP 8.2+)
  3. Backend Access Requirements

    • Admin or appropriate permissions for:
      • Extension Manager access
      • TypoScript configuration
      • Template editing

Method 1: Extension-Based Installation

Best for: Quick setup, non-technical users, standard tracking needs

google_analytics Extension (TER)

The most popular GA4 extension from the TYPO3 Extension Repository.

Installation via Extension Manager:

  1. Navigate to Extension Manager

    • Backend: Admin Tools → Extensions → Get Extensions
    • Search for "google_analytics"
    • Click Import and Install
  2. Activate the Extension

    • Admin Tools → Extensions → Installed Extensions
    • Find "google_analytics"
    • Click Activate (lightning icon)

Installation via Composer (Recommended for modern TYPO3):

composer require typo3-ter/google-analytics

Then activate via Extension Manager or CLI:

./vendor/bin/typo3 extension:activate google_analytics

Extension Configuration

Basic Setup

  1. Navigate to Extension Configuration

    • Admin Tools → Settings → Extension Configuration
    • Select "google_analytics"
  2. Enter Measurement ID

    Measurement ID: G-XXXXXXXXXX
    
  3. Configure Options

    • Cookie consent mode: Enable if using cookie consent
    • Anonymize IP: Enable for GDPR compliance
    • Exclude backend users: Enable (recommended)
    • Enable enhanced measurement: Enable for automatic events

TypoScript Configuration

The extension automatically adds TypoScript, but you can customize:

# In your site's Setup field (Web → Template → Info/Modify → Setup)
plugin.tx_googleanalytics {
    settings {
        # Override measurement ID for specific site tree
        measurementId = G-XXXXXXXXXX

        # Customize tracking behavior
        anonymizeIp = 1
        trackOutbound = 1
        trackDownloads = 1

        # Exclude specific user groups
        excludeUserGroups = 1,2,3

        # Cookie consent integration
        requireConsent = 1
        consentCookieName = tracking-consent
    }
}

Alternative Extensions

matomo_integration

For privacy-focused analytics:

Installation:

composer require leuchtfeuer/typo3-matomo-integration

Configuration:

plugin.tx_matomointegration {
    settings {
        url = https://your-matomo-instance.com/
        siteId = 1
        enableLinkTracking = 1
    }
}

Method 2: TypoScript Implementation

Best for: Advanced users, custom implementations, multi-site setups

Basic TypoScript Setup

Add to your site template (Web → Template → Info/Modify → Setup):

# Google Analytics 4 Configuration
page.headerData.100 = TEXT
page.headerData.100.value (
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX', {
    'anonymize_ip': true,
    'cookie_flags': 'SameSite=None;Secure'
  });
</script>
)

Advanced TypoScript with Conditions

# Conditional GA4 loading based on site root
[siteIdentifier = "main-site"]
page.headerData.100 = TEXT
page.headerData.100.value (
<script async src="https://www.googletagmanager.com/gtag/js?id=G-MAINSITE-ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-MAINSITE-ID', {
    'anonymize_ip': true
  });
</script>
)
[END]

[siteIdentifier = "secondary-site"]
page.headerData.100 = TEXT
page.headerData.100.value (
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SECONDARY-ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-SECONDARY-ID');
</script>
)
[END]

Exclude Backend Users

# Only load GA4 for frontend users
[frontend.user.isLoggedIn == false]
page.headerData.100 = TEXT
page.headerData.100.value (
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>
)
[END]

TypoScript with Constants

Setup Constants (Web → Template → Info/Modify → Constants):

# Google Analytics Constants
analytics {
    ga4 {
        measurementId = G-XXXXXXXXXX
        anonymizeIp = 1
        cookieFlags = SameSite=None;Secure
    }
}

Use in Setup:

page.headerData.100 = TEXT
page.headerData.100.value (
<script async src="https://www.googletagmanager.com/gtag/js?id={$analytics.ga4.measurementId}"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', '{$analytics.ga4.measurementId}', {
    'anonymize_ip': {$analytics.ga4.anonymizeIp},
    'cookie_flags': '{$analytics.ga4.cookieFlags}'
  });
</script>
)

Method 3: Fluid Template Integration

Best for: Full control, template-specific tracking, developers

Base Layout Template

Edit your main Fluid layout (typically: typo3conf/ext/your_sitepackage/Resources/Private/Layouts/Page.html):

<!DOCTYPE html>
<html lang="{language}">
<head>
    <meta charset="utf-8">
    <title>{page.title}</title>

    <!-- Google Analytics 4 -->
    <f:if condition="{settings.analytics.enabled}">
        <script async src="https://www.googletagmanager.com/gtag/js?id={settings.analytics.measurementId}"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '{settings.analytics.measurementId}', {
                'anonymize_ip': true,
                'cookie_flags': 'SameSite=None;Secure'
            });
        </script>
    </f:if>

    <f:render section="HeaderAssets" />
</head>
<body>
    <f:render section="Main" />
</body>
</html>

TypoScript Settings for Fluid

page {
    10 = FLUIDTEMPLATE
    10 {
        templateRootPaths {
            0 = EXT:your_sitepackage/Resources/Private/Templates/
        }
        partialRootPaths {
            0 = EXT:your_sitepackage/Resources/Private/Partials/
        }
        layoutRootPaths {
            0 = EXT:your_sitepackage/Resources/Private/Layouts/
        }

        settings {
            analytics {
                enabled = 1
                measurementId = G-XXXXXXXXXX
            }
        }
    }
}

Conditional Analytics Partial

Create a partial: Resources/Private/Partials/Analytics/GoogleAnalytics.html

<f:if condition="{settings.analytics.enabled}">
    <f:if condition="{TSFE.beUserLogin} == 0">
        <!-- Only load for non-backend users -->
        <script async src="https://www.googletagmanager.com/gtag/js?id={settings.analytics.measurementId}"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());

            <f:if condition="{settings.analytics.consentMode}">
                // Consent Mode v2
                gtag('consent', 'default', {
                    'analytics_storage': 'denied',
                    'ad_storage': 'denied',
                    'wait_for_update': 500
                });
            </f:if>

            gtag('config', '{settings.analytics.measurementId}', {
                'anonymize_ip': <f:if condition="{settings.analytics.anonymizeIp}" then="true" else="false" />,
                'cookie_flags': '{settings.analytics.cookieFlags}',
                'page_path': window.location.pathname
            });
        </script>
    </f:if>
</f:if>

Include in your layout:

<f:render partial="Analytics/GoogleAnalytics" arguments="{_all}" />

Method 4: Site Package Integration

Best for: Enterprise setups, version control, maintainable code

Site Package Structure

your_sitepackage/
├── Configuration/
│   ├── TypoScript/
│   │   ├── constants.typoscript
│   │   └── setup.typoscript
│   └── TCA/
├── Resources/
│   ├── Private/
│   │   ├── Layouts/
│   │   ├── Partials/
│   │   │   └── Analytics/
│   │   └── Templates/
│   └── Public/
│       └── JavaScript/
│           └── ga4-events.js
└── ext_emconf.php

constants.typoscript

# cat=analytics/enable/10; type=boolean; label=Enable Google Analytics
analytics.ga4.enabled = 1

# cat=analytics/basic/20; type=string; label=GA4 Measurement ID
analytics.ga4.measurementId = G-XXXXXXXXXX

# cat=analytics/privacy/30; type=boolean; label=Anonymize IP
analytics.ga4.anonymizeIp = 1

# cat=analytics/privacy/40; type=boolean; label=Require Cookie Consent
analytics.ga4.requireConsent = 0

# cat=analytics/advanced/50; type=string; label=Cookie Flags
analytics.ga4.cookieFlags = SameSite=None;Secure

setup.typoscript

[{$analytics.ga4.enabled} == 1]
    page {
        includeJSFooterlibs {
            ga4 = https://www.googletagmanager.com/gtag/js?id={$analytics.ga4.measurementId}
            ga4.external = 1
            ga4.async = 1
        }

        jsFooterInline {
            100 = TEXT
            100.value (
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());

                gtag('config', '{$analytics.ga4.measurementId}', {
                    'anonymize_ip': {$analytics.ga4.anonymizeIp},
                    'cookie_flags': '{$analytics.ga4.cookieFlags}'
                });
            )
        }

        # Include custom event tracking
        includeJSFooter {
            ga4Events = EXT:your_sitepackage/Resources/Public/JavaScript/ga4-events.js
        }
    }
[END]

Multi-Language and Multi-Site Configuration

Language-Specific Tracking

# Track different languages with same property
page.headerData.100 = TEXT
page.headerData.100.value (
<script>
  gtag('config', 'G-XXXXXXXXXX', {
    'language': '{site:language.twoLetterIsoCode}',
    'content_group': '{site:language.title}'
  });
</script>
)

Multi-Site Setup

Use site configurations (config/sites/*/config.yaml):

# config/sites/main/config.yaml
settings:
  analytics:
    measurementId: 'G-MAIN-SITE-ID'
    enabled: true

# config/sites/secondary/config.yaml
settings:
  analytics:
    measurementId: 'G-SECONDARY-SITE-ID'
    enabled: true

Access in TypoScript:

page.headerData.100 = TEXT
page.headerData.100.value (
<script async src="https://www.googletagmanager.com/gtag/js?id={site:settings.analytics.measurementId}"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', '{site:settings.analytics.measurementId}');
</script>
)

With cookieman Extension

[getTenv('HTTP_COOKIE') =~ '/cookieman=[^;]*trackingGoogleAnalytics[^;]*/' ]
    # User has consented to GA4
    page.headerData.100 = TEXT
    page.headerData.100.value (
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXXX');
    </script>
    )
[END]
page.headerData.100 = TEXT
page.headerData.100.value (
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  // Default consent state (denied)
  gtag('consent', 'default', {
    'analytics_storage': 'denied',
    'ad_storage': 'denied',
    'wait_for_update': 500
  });

  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<!-- After user consents -->
<script>
  document.addEventListener('cookieConsentGranted', function() {
    gtag('consent', 'update', {
      'analytics_storage': 'granted'
    });
  });
</script>
)

Caching Considerations

TYPO3's advanced caching system requires special handling:

USER_INT for Dynamic Content

If you need uncached tracking (not recommended):

page.100 = USER_INT
page.100 {
    userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
    extensionName = YourExtension
    pluginName = Analytics
}
# Cached GA4 implementation (works for 99% of cases)
page.headerData.100 = TEXT
page.headerData.100.value (
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  // Client-side detection for user-specific tracking
  gtag('config', 'G-XXXXXXXXXX', {
    'user_id': localStorage.getItem('typo3_user_id') || undefined,
    'page_path': window.location.pathname
  });
</script>
)

Performance Optimization

Preconnect to Google Domains

page {
    headerData {
        10 = TEXT
        10.value (
            <link rel="preconnect" href="https://www.google-analytics.com">
            <link rel="preconnect" href="https://www.googletagmanager.com">
        )
    }
}

Delayed Loading

page.jsFooterInline.200 = TEXT
page.jsFooterInline.200.value (
// Delay GA4 loading until user interaction
let gaLoaded = false;
const loadGA = () => {
    if (gaLoaded) return;
    gaLoaded = true;

    const script = document.createElement('script');
    script.async = true;
    script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
    document.head.appendChild(script);

    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
};

['mousedown', 'touchstart', 'scroll'].forEach(event => {
    window.addEventListener(event, loadGA, {once: true, passive: true});
});

setTimeout(loadGA, 5000); // Fallback after 5s
)

Validation and Testing

1. Clear TYPO3 Caches

Before testing:

  • Admin Tools → Maintenance → Flush Caches
  • Or CLI: ./vendor/bin/typo3 cache:flush

2. Check TypoScript Template

Web → Template → Template Analyzer

  • Search for "googletagmanager" or your Measurement ID
  • Verify the script appears in the final output

3. Frontend Preview

Web → View (or right-click page → View)

  • View page source
  • Search for your Measurement ID
  • Confirm gtag.js script loads

4. Real-Time Reports

Visit your website and check Google Analytics Real-Time reports

5. TYPO3 Debug Mode

In typo3conf/LocalConfiguration.php or Admin Tools → Settings:

'FE' => [
    'debug' => true,
],

This shows TypoScript comments in source code for debugging.

Common TYPO3-Specific Issues

Issue: GA4 Not Loading

Causes:

  • TypoScript not included in active template
  • Extension not activated
  • Cache not cleared

Solutions:

  1. Check Web → Template → Template Analyzer
  2. Verify extension in Admin Tools → Extensions
  3. Flush all caches

Issue: Tracking All Backend Users

Solution:

[backend.user.isLoggedIn == false]
    # Your GA4 code here
[END]

Issue: Multi-Site Conflicts

Solution: Use site-specific settings in config/sites/*/config.yaml

Next Steps