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:
Create GTM Container
- Sign in to tagmanager.google.com
- Create a new container for your website
- Copy your Container ID (format:
GTM-XXXXXXX)
Verify TYPO3 Version
- TYPO3 11 LTS or newer recommended
- PHP 7.4+ required
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
Recommended Extension: google_tag_manager
Installation via Extension Manager
Navigate to Extension Manager
Activate the Extension
- Admin Tools → Extensions → Installed Extensions
- Find "google_tag_manager"
- Click Activate (lightning bolt icon)
Installation via Composer (Recommended)
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
- Create GTM Server Container in Tag Manager
- Configure server environment (Google Cloud Run, AWS, etc.)
- 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>
)
Cookie Consent Integration
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]
Consent Mode v2
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
- In GTM, click Preview
- Enter your TYPO3 site URL
- Navigate site to test tags
- 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:
- Check Web → Template → Template Analyzer
- Log out of backend
- 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
- Configure GTM Data Layer for TYPO3-specific variables
- Set Up GA4 via GTM
- Debug Tracking Issues