Google Tag Manager (GTM) is the recommended method for managing analytics and marketing tags on Concrete CMS. This guide covers installation methods, best practices, and Concrete CMS-specific considerations.
Why Use GTM on Concrete CMS?
Benefits:
- Manage all tags from one interface (GA4, Meta Pixel, etc.)
- No code changes required after initial installation
- Better performance (single container vs multiple scripts)
- Easier for marketers to update without developer help
- Built-in debugging and preview tools
- Version control and workspace management
Concrete CMS-Specific Advantages:
- Works across all page types and templates
- Easy integration with form submissions
- Can exclude edit mode and dashboard pages
- Compatible with marketplace add-ons
- Survives theme updates when properly implemented
Installation Methods
| Method | Difficulty | Flexibility | Updates | Recommended |
|---|---|---|---|---|
| Header/Footer Block | Easy | Low | Dashboard | Best for most sites ✓ |
| Page Template | Medium | Medium | File editing | Custom themes |
| Theme Customization | Advanced | High | File editing | Developers |
Method 1: Header/Footer Block (Recommended)
The simplest method using Concrete CMS's built-in tracking code functionality.
Step 1: Create GTM Account and Container
Go to Google Tag Manager
Create Account
- Account Name: Your company name
- Country: Your location
Create Container
Accept Terms of Service
Copy Container Code
You'll see two code snippets:
- Head snippet: Goes in
<head> - Body snippet: Goes in
<body>
Your Container ID will look like:
GTM-XXXXXXX- Head snippet: Goes in
Step 2: Add GTM via Dashboard
Concrete CMS v9+:
Access Tracking Codes
- Log in to Dashboard
- Go to System & Settings → SEO & Statistics → Tracking Codes
Add GTM Head Code
In the "Tracking Code (Header)" field, add:
<!-- 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 -->Replace
GTM-XXXXXXXwith your actual Container ID.Add GTM Body Code
If your version supports "Tracking Code (Body)" or "Additional Scripts", add:
<!-- 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) -->Note: Some Concrete CMS versions only have header tracking codes. In that case, you'll need to add the noscript via page template (see Method 2).
Save Settings
Clear Cache
- Go to Dashboard → System & Settings → Optimization → Clear Cache
- Clear cache to ensure code appears
Step 3: Verify Installation
Use GTM Preview Mode
Check Browser Console
Open browser developer tools (F12) and run:
console.log(window.dataLayer);You should see an array (may be empty initially, but should exist).
Verify on Multiple Pages
- Homepage
- Blog posts
- Form pages
- Landing pages
Method 2: Page Template Installation
For more control or if built-in tracking codes aren't sufficient.
Step 1: Locate Theme Files
Your theme files are typically in:
/application/themes/[your-theme-name]/
Or for package themes:
/packages/[package-name]/themes/[theme-name]/
Step 2: Edit Page Template
Open your theme's main layout file (often view.php, default.php, or page_theme.php).
Add GTM Head Code:
Find the </head> closing tag and add above it:
<?php
// Exclude GTM from edit mode and dashboard
if (!$c->isEditMode() && !$this->controller->isControllerTaskInstanceOf('DashboardPageController')) {
?>
<!-- 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 -->
<?php
}
?>
</head>
Add GTM Body Code:
Find the opening <body> tag and add immediately after it:
<body>
<?php
if (!$c->isEditMode() && !$this->controller->isControllerTaskInstanceOf('DashboardPageController')) {
?>
<!-- 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) -->
<?php
}
?>
<!-- rest of body content -->
Replace GTM-XXXXXXX with your actual Container ID in both places.
Step 3: Clear Cache and Test
- Dashboard → System & Settings → Optimization → Clear Cache
- Visit your site and verify GTM loads
Method 3: Theme Customization (Advanced)
For developers who want maximum control.
Using Concrete CMS Page Event
Add GTM code via page event in your theme's page_theme.php:
<?php
namespace Application\Theme\YourTheme;
use Concrete\Core\Page\Theme\Theme;
class PageTheme extends Theme
{
public function registerAssets()
{
// Register assets if needed
}
public function getThemeName()
{
return t('Your Theme Name');
}
public function getThemeDescription()
{
return t('Your theme description');
}
}
Then in your view files, add conditional GTM loading.
Using Header/Footer Assets
In your theme's elements/header.php or elements/footer.php:
<?php
$c = Page::getCurrentPage();
$app = \Concrete\Core\Support\Facade\Application::getFacadeApplication();
$u = $app->make(\Concrete\Core\User\User::class);
// Only load on public pages
if (!$c->isEditMode() &&
!$this->controller->isControllerTaskInstanceOf('DashboardPageController') &&
!$u->isSuperUser()) {
?>
<!-- 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 -->
<?php
}
?>
Excluding Admin and Edit Pages
Always exclude these from GTM tracking:
<?php
$c = Page::getCurrentPage();
// Check if NOT in edit mode AND NOT in dashboard
if (!$c->isEditMode() &&
!$this->controller->isControllerTaskInstanceOf('DashboardPageController')) {
// Add GTM code here
}
?>
Additional exclusions (optional):
<?php
$app = \Concrete\Core\Support\Facade\Application::getFacadeApplication();
$c = Page::getCurrentPage();
$u = $app->make(\Concrete\Core\User\User::class);
// Exclude edit mode, dashboard, and super users
if (!$c->isEditMode() &&
!$this->controller->isControllerTaskInstanceOf('DashboardPageController') &&
!$u->isSuperUser()) {
// Add GTM code
}
?>
Configure GTM for Concrete CMS
Once GTM is installed, configure it to work with Concrete CMS.
1. Enable Built-in Variables
In GTM:
- Go to Variables → Configure
- Enable these built-in variables:
- Page URL
- Page Path
- Page Hostname
- Referrer
- Click Element
- Click Classes
- Click ID
- Click URL
- Click Text
- Form Element
- Form Classes
- Form ID
2. Create Concrete CMS Data Layer Variables
See Concrete CMS Data Layer Documentation for full variable setup.
Common variables to create:
Page Type:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
pageType - Name:
CMS - Page Type
Page ID:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
pageID - Name:
CMS - Page ID
User Type:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
userType - Name:
CMS - User Type
3. Create Concrete CMS Event Triggers
Form Submission:
- Type: Form Submission
- Name:
Concrete CMS - Form Submit - Fires on: All Forms
File Download:
- Type: Click - All Elements
- Name:
Concrete CMS - File Download - Fires on: Some Clicks
- Condition: Click URL matches RegEx
\.(pdf|doc|docx|xls|xlsx|zip)$
External Links:
- Type: Click - All Elements
- Name:
Concrete CMS - External Link - Fires on: Some Clicks
- Condition: Click URL does not contain
\{\{Page Hostname\}\}
4. Publish Your Container
- Click Submit in GTM
- Name the version (e.g., "Initial Concrete CMS Setup")
- Add description
- Click Publish
Initialize Concrete CMS Data Layer
Add to your page template before GTM code:
<?php
if (!$c->isEditMode() && !$this->controller->isControllerTaskInstanceOf('DashboardPageController')) {
$app = \Concrete\Core\Support\Facade\Application::getFacadeApplication();
$u = $app->make(\Concrete\Core\User\User::class);
$pageType = $c->getPageTypeHandle();
$userType = $u->isRegistered() ? 'registered' : 'guest';
?>
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'pageType': '<?php echo $pageType; ?>',
'pageID': '<?php echo $c->getCollectionID(); ?>',
'pageName': '<?php echo addslashes($c->getCollectionName()); ?>',
'userType': '<?php echo $userType; ?>',
'userID': '<?php echo $u->getUserID(); ?>'
});
</script>
<?php
}
?>
Place this before the GTM container code.
Testing & Debugging
Use GTM Preview Mode
- In GTM, click Preview
- Enter your Concrete CMS site URL
- Click Connect
- GTM Tag Assistant opens in new window
- Navigate your site and watch events fire in real-time
Debug Checklist
- GTM container loads on all public pages
- Container does NOT load in edit mode
- Container does NOT load on dashboard pages
- Data layer variables populate correctly
- Custom triggers fire correctly
- Tags send data to analytics platforms
- No JavaScript errors in console
Common Debug Commands
Check if GTM is loaded:
console.log(window.google_tag_manager);
// Should show object with your container ID
View data layer:
console.log(window.dataLayer);
// Should show array with pushed data
Monitor data layer pushes:
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('Data Layer Push:', arguments[0]);
originalPush.apply(window.dataLayer, arguments);
};
Performance Optimization
1. Minimize Container Size
- Remove unused tags, triggers, and variables
- Avoid adding too many tags to single trigger
- Use tag sequencing for dependent tags
- Regularly audit and clean up
2. Use Async Loading
GTM loads asynchronously by default. Don't modify this unless absolutely necessary.
3. Monitor Impact
GTM can impact Concrete CMS performance if not optimized:
- Monitor Largest Contentful Paint (LCP)
- Check Cumulative Layout Shift (CLS)
- Keep container size under 200KB when possible
4. Consolidate Tags
Instead of installing multiple tracking scripts:
- Install only GTM in theme
- Add all tracking pixels through GTM
- Remove marketplace tracking add-ons that duplicate functionality
Privacy & Consent Management
Cookie Consent Integration
Wait for user consent before firing marketing tags:
<script>
window.dataLayer = window.dataLayer || [];
// Check for consent cookie
const hasConsent = document.cookie.indexOf('tracking_consent=true') !== -1;
dataLayer.push({
'event': 'consent_status',
'analytics_consent': hasConsent ? 'granted' : 'denied',
'marketing_consent': hasConsent ? 'granted' : 'denied'
});
// Listen for consent changes
document.addEventListener('consent_updated', function(e) {
dataLayer.push({
'event': 'consent_update',
'analytics_consent': e.detail.analytics ? 'granted' : 'denied',
'marketing_consent': e.detail.marketing ? 'granted' : 'denied'
});
});
</script>
In GTM, create triggers based on these consent events to control when tags fire.
Troubleshooting
GTM Container Not Loading
Check:
- Container ID is correct (
GTM-XXXXXXX) - Script is in both
<head>and<body> - No JavaScript errors blocking execution
- Concrete CMS cache cleared
- Not in edit mode or dashboard
- Browser cache cleared
Data Layer Empty
Diagnosis:
console.log(window.dataLayer);
// Should return array, not undefined
If undefined:
- GTM not properly installed
- JavaScript errors blocking initialization
- Cache not cleared
Events Not Firing
See Events Not Firing Troubleshooting for detailed debugging.
Container Loads in Edit Mode
Problem: GTM tracking when editing pages.
Solution: Add edit mode check:
<?php if (!$c->isEditMode()) { ?>
<!-- GTM code -->
<?php } ?>
Changes Not Appearing
Problem: Updated GTM code doesn't show.
Solution: Clear Concrete CMS cache:
- Dashboard → System & Settings → Optimization → Clear Cache
- Or CLI:
concrete/bin/concrete5 c5:clear-cache
Next Steps
- Concrete CMS Data Layer - Complete data layer reference
- GA4 Setup via GTM - Configure GA4 tags
- Meta Pixel Setup - Add Meta Pixel through GTM
For general GTM concepts, see Google Tag Manager Guide.