Learn how to track Concrete CMS-specific events in Google Analytics 4, including form submissions, file downloads, user interactions, and custom block events.
Event Tracking Methods
Using Direct gtag.js Implementation
Add event tracking directly in your theme templates or custom blocks.
Using Google Tag Manager (Recommended)
GTM provides easier management and testing of events. See GTM Setup.
Common Concrete CMS Events
Form Submissions
Track form block submissions (most common use case).
Method 1: GTM Auto-Event Listener (Easiest)
If using GTM, create a form submission trigger:
- GTM → Triggers → New
- Trigger Type: Form Submission
- Name:
Concrete CMS - Form Submit - This trigger fires on: All Forms (or Some Forms with filter)
- Save
- GTM → Tags → New
- Tag Configuration: Google Analytics: GA4 Event
- Event Name:
form_submit - Event Parameters:
form_name:\{\{Form ID\}\}or\{\{Form Classes\}\}form_destination:\{\{Page URL\}\}
- Triggering:
Concrete CMS - Form Submit - Save and Publish
Method 2: Custom JavaScript in Theme
Add to your theme's main JavaScript file or page template:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track Concrete CMS form blocks
const forms = document.querySelectorAll('form[action*="/ccm/system/form/submit"]');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formName = form.getAttribute('data-form-name') ||
form.querySelector('.ccm-form-name')?.textContent ||
'Unknown Form';
gtag('event', 'form_submit', {
'form_name': formName,
'form_id': form.id || 'no-id',
'page_path': window.location.pathname
});
});
});
});
</script>
Method 3: Form Block Template Override
Create custom form block template with tracking:
Location: /application/blocks/form/templates/custom_tracking.php
<?php
// Include default form template
$this->inc('view.php');
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('#<?php echo $form_instance_id; ?>');
if (form) {
form.addEventListener('submit', function(e) {
gtag('event', 'form_submit', {
'form_name': '<?php echo addslashes($form_name); ?>',
'form_id': '<?php echo $form_instance_id; ?>',
'event_category': 'Form',
'event_label': '<?php echo addslashes($form_name); ?>'
});
});
}
});
</script>
Then select "Custom Tracking" template when adding form block.
File Downloads
Track downloads from file manager and document library blocks.
GTM Click Trigger Method
- Create Trigger → Click - All Elements
- Trigger Fires On: Some Clicks
- Conditions:
- Click URL matches RegEx:
\.(pdf|doc|docx|xls|xlsx|zip|mp4|mp3)$
- Click URL matches RegEx:
- Name:
Concrete CMS - File Download
Create GA4 Event Tag:
- Event Name:
file_download - Event Parameters:
file_name:\{\{Click Text\}\}file_extension: Use custom JavaScript variable to extract extensionlink_url:\{\{Click URL\}\}
- Trigger:
Concrete CMS - File Download
Direct Implementation
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track file downloads
const fileLinks = document.querySelectorAll('a[href*="/download_file/"]');
fileLinks.forEach(function(link) {
link.addEventListener('click', function(e) {
const fileName = link.textContent.trim() || link.href.split('/').pop();
const fileExtension = fileName.split('.').pop().toLowerCase();
gtag('event', 'file_download', {
'file_name': fileName,
'file_extension': fileExtension,
'link_url': link.href,
'link_text': link.textContent.trim()
});
});
});
});
</script>
External Link Clicks
Track when users click external links.
<script>
document.addEventListener('DOMContentLoaded', function() {
const currentDomain = window.location.hostname;
document.querySelectorAll('a[href^="http"]').forEach(function(link) {
link.addEventListener('click', function(e) {
const linkDomain = new URL(link.href).hostname;
// Only track external links
if (linkDomain !== currentDomain) {
gtag('event', 'click', {
'event_category': 'Outbound Link',
'event_label': link.href,
'link_text': link.textContent.trim(),
'link_domain': linkDomain
});
}
});
});
});
</script>
Video Interactions (YouTube Block)
Track YouTube video blocks in Concrete CMS.
<script>
// Track YouTube video plays from Concrete CMS YouTube block
window.onYouTubeIframeAPIReady = function() {
const players = [];
document.querySelectorAll('iframe[src*="youtube.com"]').forEach(function(iframe, index) {
const player = new YT.Player(iframe, {
events: {
'onStateChange': function(event) {
const videoTitle = iframe.title || iframe.getAttribute('data-title') || 'Unknown Video';
if (event.data === YT.PlayerState.PLAYING) {
gtag('event', 'video_start', {
'video_title': videoTitle,
'video_provider': 'YouTube'
});
}
if (event.data === YT.PlayerState.ENDED) {
gtag('event', 'video_complete', {
'video_title': videoTitle,
'video_provider': 'YouTube'
});
}
}
}
});
players.push(player);
});
};
</script>
Page Type Tracking
Track different page types as custom events:
<?php
if (!$c->isEditMode() && !$this->controller->isControllerTaskInstanceOf('DashboardPageController')) {
$pageType = $c->getPageTypeHandle();
$pageName = $c->getCollectionName();
?>
<script>
gtag('event', 'page_type_view', {
'page_type': '<?php echo $pageType; ?>',
'page_name': '<?php echo addslashes($pageName); ?>',
'content_group': '<?php echo $pageType; ?>'
});
</script>
<?php
}
?>
Blog Post Tracking
Track blog post views with custom dimensions:
<?php
if ($c->getPageTypeHandle() === 'blog_entry') {
$author = $c->getVersionObject()->getVersionAuthorUserName();
$datePublished = $c->getCollectionDatePublic('Y-m-d');
?>
<script>
gtag('event', 'view_item', {
'item_id': '<?php echo $c->getCollectionID(); ?>',
'item_name': '<?php echo addslashes($c->getCollectionName()); ?>',
'item_category': 'Blog Post',
'item_brand': 'Blog',
'content_type': 'blog_post',
'author': '<?php echo addslashes($author); ?>',
'publish_date': '<?php echo $datePublished; ?>'
});
</script>
<?php
}
?>
E-Commerce Tracking (Community Store)
If using the Community Store add-on for e-commerce:
Product View Event
Add to product page template or block:
<?php
// Assuming $product is the Community Store product object
if (isset($product) && !$c->isEditMode()) {
?>
<script>
gtag('event', 'view_item', {
'currency': '<?php echo \Config::get('community_store.currency'); ?>',
'value': <?php echo $product->getPrice(); ?>,
'items': [{
'item_id': '<?php echo $product->getID(); ?>',
'item_name': '<?php echo addslashes($product->getName()); ?>',
'item_category': '<?php echo addslashes($product->getGroupName()); ?>',
'price': <?php echo $product->getPrice(); ?>,
'quantity': 1
}]
});
</script>
<?php
}
?>
Add to Cart Event
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen for Community Store add to cart
document.querySelectorAll('form.store-form-add-to-cart').forEach(function(form) {
form.addEventListener('submit', function(e) {
const productId = form.querySelector('input[name="pID"]')?.value;
const productName = form.querySelector('.product-name')?.textContent;
const productPrice = form.querySelector('.product-price')?.getAttribute('data-price');
const quantity = form.querySelector('input[name="quantity"]')?.value || 1;
gtag('event', 'add_to_cart', {
'currency': 'USD', // Set your currency
'value': parseFloat(productPrice) * parseInt(quantity),
'items': [{
'item_id': productId,
'item_name': productName,
'quantity': parseInt(quantity),
'price': parseFloat(productPrice)
}]
});
});
});
});
</script>
Purchase Event
Add to order confirmation page template:
<?php
// On order confirmation page
if (isset($order) && !$c->isEditMode()) {
$items = [];
foreach ($order->getOrderItems() as $item) {
$items[] = [
'item_id' => $item->getProductID(),
'item_name' => $item->getProductName(),
'price' => $item->getPrice(),
'quantity' => $item->getQuantity()
];
}
?>
<script>
gtag('event', 'purchase', {
'transaction_id': '<?php echo $order->getOrderID(); ?>',
'value': <?php echo $order->getTotal(); ?>,
'currency': '<?php echo \Config::get('community_store.currency'); ?>',
'tax': <?php echo $order->getTaxTotal(); ?>,
'shipping': <?php echo $order->getShippingTotal(); ?>,
'items': <?php echo json_encode($items); ?>
});
</script>
<?php
}
?>
Custom Block Event Tracking
For custom blocks, add tracking to block's view template.
Example: Custom CTA Block
<?php defined('C5_EXECUTE') or die('Access Denied.'); ?>
<div class="custom-cta-block" id="cta-<?php echo $bID; ?>">
<a href="<?php echo $ctaUrl; ?>" class="btn btn-primary cta-button">
<?php echo $ctaText; ?>
</a>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const ctaButton = document.querySelector('#cta-<?php echo $bID; ?> .cta-button');
if (ctaButton) {
ctaButton.addEventListener('click', function(e) {
gtag('event', 'cta_click', {
'event_category': 'CTA',
'event_label': '<?php echo addslashes($ctaText); ?>',
'link_url': '<?php echo $ctaUrl; ?>',
'block_id': '<?php echo $bID; ?>'
});
});
}
});
</script>
User Registration and Login
Track user registrations and logins:
Registration Event
Add to registration page template or success page:
<?php
// After successful registration
if (!$c->isEditMode()) {
?>
<script>
gtag('event', 'sign_up', {
'method': 'Concrete CMS Registration',
'event_category': 'User',
'event_label': 'New Registration'
});
</script>
<?php
}
?>
Login Event
Add to login form or post-login page:
<script>
// Track login form submission
document.querySelector('form[action*="/login/authenticate"]')?.addEventListener('submit', function(e) {
gtag('event', 'login', {
'method': 'Concrete CMS Login',
'event_category': 'User'
});
});
</script>
Search Tracking
Track site searches using Concrete CMS search:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if this is a search results page
const urlParams = new URLSearchParams(window.location.search);
const searchQuery = urlParams.get('query') || urlParams.get('search');
if (searchQuery) {
gtag('event', 'search', {
'search_term': searchQuery,
'page_location': window.location.href
});
}
// Track search form submissions
document.querySelectorAll('form[action*="/search"]').forEach(function(form) {
form.addEventListener('submit', function(e) {
const searchInput = form.querySelector('input[name="query"], input[name="search"]');
if (searchInput) {
gtag('event', 'search', {
'search_term': searchInput.value
});
}
});
});
});
</script>
Scroll Depth Tracking
Track how far users scroll on pages:
<script>
(function() {
let scrollDepths = [25, 50, 75, 90, 100];
let tracked = {};
window.addEventListener('scroll', function() {
const scrollPercent = Math.round(
((window.scrollY + window.innerHeight) / document.body.scrollHeight) * 100
);
scrollDepths.forEach(function(depth) {
if (scrollPercent >= depth && !tracked[depth]) {
tracked[depth] = true;
gtag('event', 'scroll', {
'event_category': 'Scroll Depth',
'event_label': depth + '%',
'value': depth,
'page_path': window.location.pathname
});
}
});
});
})();
</script>
Testing & Debugging
1. Use GA4 DebugView
Enable debug mode:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Then check Admin → DebugView in GA4.
2. Check Browser Console
Monitor data layer in browser console:
// View all data layer pushes
console.log(window.dataLayer);
// Listen for new pushes
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('Data Layer Push:', arguments[0]);
originalPush.apply(window.dataLayer, arguments);
};
3. GTM Preview Mode
If using GTM:
- Click Preview in GTM
- Enter your Concrete CMS site URL
- Navigate and trigger events
- Verify events appear in Tag Assistant
4. Verify Event Parameters
In GA4 DebugView:
- Check event name matches GA4 recommended events
- Verify parameters are present and correctly formatted
- Ensure values are numeric (no currency symbols)
- Check that item arrays are properly structured
Common Issues
Events Fire in Edit Mode
Problem: Events tracking when editing pages.
Solution: Always check edit mode:
<?php if (!$c->isEditMode()) { ?>
// Event tracking code
<?php } ?>
Form Submissions Not Tracked
Problem: Forms submit before event fires.
Solution: Add delay or use beacon:
form.addEventListener('submit', function(e) {
e.preventDefault();
gtag('event', 'form_submit', {
'event_callback': function() {
form.submit();
}
});
});
Cache Prevents Updated Tracking
Problem: Event code changes don't appear.
Solution: Clear Concrete CMS cache:
- Dashboard → System & Settings → Optimization → Clear Cache
Next Steps
- Concrete CMS Data Layer - Set up custom data layer
- GTM Setup - Install GTM for easier event management
- Events Not Firing - Troubleshooting guide
For general event tracking concepts, see GA4 Event Tracking Guide.