Installing Google Tag Manager on TYPO3 | OpsBlu Docs

Installing Google Tag Manager on TYPO3

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

This guide covers TYPO3-specific methods for implementing Google Tag Manager (GTM), from extension-based installations to custom TypoScript and Fluid template integrations.

Prerequisites

Before installing GTM on TYPO3:

  1. Create GTM Container

  2. Verify TYPO3 Version

    • TYPO3 11 LTS or newer recommended
    • PHP 7.4+ required
  3. Backend Access Requirements

    • Extension Manager access
    • TypoScript configuration permissions
    • Template editing capabilities

Method 1: Extension-Based Installation

Best for: Quick setup, non-technical users, standard implementations

Installation via Extension Manager

  1. Navigate to Extension Manager

    • Admin Tools → Extensions → Get Extensions
    • Search for "google tag manager" or "gtm"
    • Click Import and Install
  2. Activate the Extension

    • Admin Tools → Extensions → Installed Extensions
    • Find "google_tag_manager"
    • Click Activate (lightning bolt icon)
composer require typo3-ter/google-tag-manager

Activate via CLI:

./vendor/bin/typo3 extension:activate google_tag_manager

Extension Configuration

Admin Tools → Settings → Extension Configuration → google_tag_manager

Configure the following:

Container ID: GTM-XXXXXXX
Enable tracking: Yes
Exclude backend users: Yes
Data layer name: dataLayer (default)
Cookie consent integration: Enabled (if applicable)

TypoScript Configuration for Extension

plugin.tx_googletagmanager {
    settings {
        # Override container ID for specific site
        containerId = GTM-XXXXXXX

        # Customize data layer
        dataLayerName = dataLayer

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

Method 2: TypoScript Implementation

Best for: Full control, multi-site setups, custom implementations

Basic TypoScript Setup

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

# Google Tag Manager - Head Section
page.headerData.300 = TEXT
page.headerData.300.value (
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
)

# Google Tag Manager - Body Section
page.bodyTagCObject = COA
page.bodyTagCObject {
    10 = TEXT
    10.value (
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
    )
}

TypoScript with Constants

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

# Google Tag Manager Configuration
gtm {
    containerId = GTM-XXXXXXX
    enabled = 1
    excludeBackendUsers = 1
    dataLayerName = dataLayer
}

Setup:

[{$gtm.enabled} == 1]
    # Exclude backend users if configured
    [backend.user.isLoggedIn == false || {$gtm.excludeBackendUsers} == 0]

        # GTM Head Code
        page.headerData.300 = TEXT
        page.headerData.300.value (
        <!-- Google Tag Manager -->
        <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','{$gtm.dataLayerName}','{$gtm.containerId}');</script>
        <!-- End Google Tag Manager -->
        )

        # GTM Body Code
        page.bodyTagCObject = COA
        page.bodyTagCObject {
            10 = TEXT
            10.value (
            <!-- Google Tag Manager (noscript) -->
            <noscript><iframe src="https://www.googletagmanager.com/ns.html?id={$gtm.containerId}"
            height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
            <!-- End Google Tag Manager (noscript) -->
            )
        }

    [END]
[END]

Multi-Site Configuration

For different sites with different containers:

# Main site
[siteIdentifier = "main-site"]
page.headerData.300 = TEXT
page.headerData.300.value (
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MAIN123');</script>
)
[END]

# Secondary site
[siteIdentifier = "secondary-site"]
page.headerData.300 = TEXT
page.headerData.300.value (
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-SEC456');</script>
)
[END]

Site Configuration YAML Approach

Store container ID in config/sites/main/config.yaml:

base: 'https://www.example.com/'
languages:
  - languageId: 0
    title: English
    base: /
    locale: en_US.UTF-8

settings:
  gtm:
    containerId: 'GTM-XXXXXXX'
    enabled: true
    dataLayerName: 'dataLayer'

Access in TypoScript:

page.headerData.300 = TEXT
page.headerData.300.value (
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','{site:settings.gtm.dataLayerName}','{site:settings.gtm.containerId}');</script>
)

page.bodyTagCObject = COA
page.bodyTagCObject {
    10 = TEXT
    10.stdWrap.dataWrap (
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id={site:settings.gtm.containerId}"
    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    )
}

Method 3: Fluid Template Integration

Best for: Developers, full template control, conditional loading

Main Layout Template

Edit typo3conf/ext/your_sitepackage/Resources/Private/Layouts/Page.html:

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

    <!-- Google Tag Manager -->
    <f:if condition="{settings.gtm.enabled}">
        <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','{settings.gtm.dataLayerName}','{settings.gtm.containerId}');</script>
    </f:if>
    <!-- End Google Tag Manager -->

    <f:render section="HeaderAssets" />
</head>
<body>
    <!-- Google Tag Manager (noscript) -->
    <f:if condition="{settings.gtm.enabled}">
        <noscript>
            <iframe src="https://www.googletagmanager.com/ns.html?id={settings.gtm.containerId}"
                    height="0" width="0" style="display:none;visibility:hidden"></iframe>
        </noscript>
    </f:if>
    <!-- End Google Tag Manager (noscript) -->

    <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 {
            gtm {
                enabled = 1
                containerId = GTM-XXXXXXX
                dataLayerName = dataLayer
            }
        }
    }
}

GTM Partial Component

Create Resources/Private/Partials/Analytics/GTM.html:

<f:if condition="{settings.gtm.enabled}">
    <f:comment><!-- Exclude backend users --></f:comment>
    <f:if condition="{TSFE.beUserLogin} == 0">
        <f:comment><!-- Head Section --></f:comment>
        <f:section name="head">
            <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','{settings.gtm.dataLayerName}','{settings.gtm.containerId}');</script>
        </f:section>

        <f:comment><!-- Body Section --></f:comment>
        <f:section name="body">
            <noscript>
                <iframe src="https://www.googletagmanager.com/ns.html?id={settings.gtm.containerId}"
                        height="0" width="0" style="display:none;visibility:hidden"></iframe>
            </noscript>
        </f:section>
    </f:if>
</f:if>

Include in layout:

<head>
    <f:render partial="Analytics/GTM" section="head" arguments="{_all}" />
</head>
<body>
    <f:render partial="Analytics/GTM" section="body" arguments="{_all}" />
</body>

Method 4: Site Package Integration

Best for: Enterprise, version control, professional deployments

Site Package Structure

your_sitepackage/
├── Configuration/
│   ├── TypoScript/
│   │   ├── constants.typoscript
│   │   └── setup.typoscript
│   └── TCA/
├── Resources/
│   ├── Private/
│   │   ├── Layouts/
│   │   │   └── Page.html
│   │   ├── Partials/
│   │   │   └── Analytics/
│   │   │       └── GTM.html
│   │   └── Templates/
│   └── Public/
│       └── JavaScript/
│           └── gtm-data-layer.js
└── ext_emconf.php

constants.typoscript

# cat=gtm/enable/10; type=boolean; label=Enable Google Tag Manager
gtm.enabled = 1

# cat=gtm/basic/20; type=string; label=GTM Container ID
gtm.containerId = GTM-XXXXXXX

# cat=gtm/basic/30; type=string; label=Data Layer Name
gtm.dataLayerName = dataLayer

# cat=gtm/privacy/40; type=boolean; label=Exclude Backend Users
gtm.excludeBackendUsers = 1

# cat=gtm/privacy/50; type=boolean; label=Require Cookie Consent
gtm.requireConsent = 0

setup.typoscript

[{$gtm.enabled} == 1]
    [backend.user.isLoggedIn == false || {$gtm.excludeBackendUsers} == 0]

        # GTM Head Code
        page.headerData.300 = TEXT
        page.headerData.300.value (
        <!-- Google Tag Manager -->
        <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','{$gtm.dataLayerName}','{$gtm.containerId}');</script>
        <!-- End Google Tag Manager -->
        )

        # GTM Body Code
        page.bodyTagCObject = COA
        page.bodyTagCObject {
            10 = TEXT
            10.value (
            <!-- Google Tag Manager (noscript) -->
            <noscript><iframe src="https://www.googletagmanager.com/ns.html?id={$gtm.containerId}"
            height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
            <!-- End Google Tag Manager (noscript) -->
            )
        }

        # Include data layer JavaScript
        page.includeJSFooter {
            gtmDataLayer = EXT:your_sitepackage/Resources/Public/JavaScript/gtm-data-layer.js
            gtmDataLayer.defer = 1
        }

    [END]
[END]

Server-Side GTM Implementation

For advanced server-side tagging:

Server Container Setup

  1. Create GTM Server Container in Tag Manager
  2. Configure server environment (Google Cloud Run, AWS, etc.)
  3. Update client container to use server URL

TypoScript for Server-Side GTM

page.headerData.300 = TEXT
page.headerData.300.value (
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://your-server-domain.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
)

With cookieman Extension

# Only load GTM after consent
[getTenv('HTTP_COOKIE') =~ '/cookieman=[^;]*trackingGoogleTagManager[^;]*/' ]
    page.headerData.300 = TEXT
    page.headerData.300.value (
    <script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
    )
[END]
page.headerData.299 = TEXT
page.headerData.299.value (
<!-- Consent Mode -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}

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

# Then load GTM as normal
page.headerData.300 = TEXT
page.headerData.300.value (...)

Update consent after user acceptance:

// After user grants consent
gtag('consent', 'update', {
    'analytics_storage': 'granted',
    'ad_storage': 'granted'
});

Environment-Specific Containers

Use different containers for development, staging, and production:

# Development
[applicationContext = Development]
    gtm.containerId = GTM-DEV1234
[END]

# Staging
[applicationContext = Production/Staging]
    gtm.containerId = GTM-STG5678
[END]

# Production
[applicationContext = Production]
    gtm.containerId = GTM-PROD999
[END]

Performance Optimization

Preconnect to GTM Domains

page.headerData.10 = TEXT
page.headerData.10.value (
    <link rel="preconnect" href="https://www.googletagmanager.com">
    <link rel="dns-prefetch" href="https://www.googletagmanager.com">
)

Delayed GTM Loading

page.jsFooterInline.500 = TEXT
page.jsFooterInline.500.value (
// Delay GTM loading until user interaction
let gtmLoaded = false;
const loadGTM = () => {
    if (gtmLoaded) return;
    gtmLoaded = true;

    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-XXXXXXX');
};

// Load on user interaction
['mousedown', 'touchstart', 'scroll', 'keydown'].forEach(event => {
    window.addEventListener(event, loadGTM, {once: true, passive: true});
});

// Fallback: load after 3 seconds
setTimeout(loadGTM, 3000);
)

Validation and Testing

1. GTM Preview Mode

  1. In GTM, click Preview
  2. Enter your TYPO3 site URL
  3. Navigate site to test tags
  4. Verify tags fire correctly

2. Clear TYPO3 Caches

Before testing:

./vendor/bin/typo3 cache:flush

Or: Admin Tools → Maintenance → Flush Caches

3. Browser DevTools

// Check if dataLayer exists (browser console)
console.log(window.dataLayer);

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

4. Google Tag Assistant

Use Tag Assistant:

  • Connect to your site
  • Verify GTM container loads
  • Check for configuration errors

Common TYPO3-Specific Issues

Issue: GTM Not Loading

Causes:

  • TypoScript not in active template
  • Backend user logged in (if excluded)
  • Cache not cleared

Solutions:

  1. Check Web → Template → Template Analyzer
  2. Log out of backend
  3. Flush all caches

Issue: dataLayer Not Found

Cause: GTM code in wrong location

Solution: Ensure GTM is in <head> section:

page.headerData.300 = TEXT  # Correct
page.jsFooterInline.300 = TEXT  # Wrong - too late

Issue: Noscript Tag Not Rendering

Cause: Missing body tag configuration

Solution:

page.bodyTagCObject = COA
page.bodyTagCObject {
    10 = TEXT
    10.value (
    <noscript>...</noscript>
    )
}

Next Steps