Learn how to track TYPO3-specific user interactions with GA4 events, including form submissions (Powermail, Form Framework), content elements, downloads, and custom user journeys using TypoScript and Fluid.
Understanding TYPO3 Event Tracking
TYPO3 sites generate unique interaction patterns that require specialized tracking:
- Form Framework submissions (TYPO3 Core forms)
- Powermail forms (most popular form extension)
- Content element interactions (accordions, tabs, sliders)
- File downloads (FAL-managed files)
- News/Blog interactions (EXT:news, EXT:blog)
- Backend-to-Frontend transitions
Enhanced Measurement (Automatic Events)
GA4's Enhanced Measurement automatically tracks common events when enabled in your GA4 property settings.
Automatic Events Available
page_view- Every page loadscroll- 90% scroll depthclick- Outbound link clicksview_search_results- TYPO3 indexed_search resultsvideo_start,video_progress,video_complete- Embedded videosfile_download- FAL file downloads
TYPO3-Specific Search Tracking
# Track TYPO3 indexed_search
[request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword'] != '']
page.jsFooterInline.110 = TEXT
page.jsFooterInline.110.stdWrap.dataWrap (
gtag('event', 'search', {
'search_term': '{request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword']}'
});
)
[END]
Form Framework (TYPO3 Core) Tracking
TYPO3's native Form Framework provides hooks for event tracking.
TypoScript Configuration
plugin.tx_form {
settings {
yamlConfigurations {
100 = EXT:your_sitepackage/Configuration/Form/CustomSetup.yaml
}
}
}
Form YAML Configuration
Create Configuration/Form/CustomSetup.yaml:
TYPO3:
CMS:
Form:
prototypes:
standard:
finishersDefinition:
GoogleAnalytics:
implementationClassName: 'Vendor\Extension\Finisher\GoogleAnalyticsFinisher'
Custom Finisher (PHP)
Create Classes/Finisher/GoogleAnalyticsFinisher.php:
<?php
declare(strict_types=1);
namespace Vendor\Extension\Finisher;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
class GoogleAnalyticsFinisher extends AbstractFinisher
{
protected function executeInternal()
{
$formRuntime = $this->finisherContext->getFormRuntime();
$formIdentifier = $formRuntime->getIdentifier();
$formTitle = $formRuntime->getFormDefinition()->getLabel();
// Add JavaScript to page for GA4 event
$GLOBALS['TSFE']->additionalFooterData['ga4_form_' . $formIdentifier] = '
<script>
gtag("event", "form_submit", {
"event_category": "TYPO3 Form",
"event_label": "' . htmlspecialchars($formTitle) . '",
"form_id": "' . htmlspecialchars($formIdentifier) . '",
"form_name": "' . htmlspecialchars($formTitle) . '"
});
</script>';
return null;
}
}
JavaScript-Based Form Tracking
page.jsFooterInline.120 = TEXT
page.jsFooterInline.120.value (
// Track TYPO3 Form Framework submissions
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('[data-type="finisher"]');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formId = this.getAttribute('id');
const formName = this.querySelector('h2')?.textContent || 'Unknown Form';
gtag('event', 'form_submit', {
'event_category': 'TYPO3 Form Framework',
'event_label': formName,
'form_id': formId,
'form_name': formName
});
});
});
});
)
Powermail Form Tracking
Powermail is TYPO3's most popular form extension with extensive tracking capabilities.
JavaScript Event Listener
page.jsFooterInline.130 = TEXT
page.jsFooterInline.130.value (
// Track Powermail submissions
document.addEventListener('DOMContentLoaded', function() {
const powermailForms = document.querySelectorAll('.powermail_form');
powermailForms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const formUid = this.getAttribute('data-powermail-uid');
const formName = this.querySelector('.powermail_fieldset legend')?.textContent || 'Contact Form';
gtag('event', 'form_submit', {
'event_category': 'Powermail',
'event_label': formName,
'form_id': formUid,
'form_name': formName,
'value': 1
});
});
});
// Track field interactions
document.querySelectorAll('.powermail_field input, .powermail_field select').forEach(function(field) {
field.addEventListener('focus', function() {
gtag('event', 'form_start', {
'event_category': 'Powermail',
'event_label': 'User Started Form',
'form_id': this.closest('.powermail_form').getAttribute('data-powermail-uid')
});
}, {once: true});
});
});
)
Powermail TypoScript Hook
plugin.tx_powermail {
settings {
setup {
marketing {
# Enable Google Analytics tracking
googleAnalytics {
_enable = 1
measurementId = {$analytics.ga4.measurementId}
}
}
}
}
}
Ajax Form Submission Tracking
page.includeJSFooter.powermail = EXT:your_sitepackage/Resources/Public/JavaScript/powermail-ga4.js
Create Resources/Public/JavaScript/powermail-ga4.js:
// Track Powermail AJAX submissions
jQuery(document).on('powermail:submit:success', function(e, data) {
const formUid = data.formUid || 'unknown';
const formName = jQuery('[data-powermail-uid="' + formUid + '"]')
.find('legend').first().text() || 'Contact Form';
gtag('event', 'form_submit', {
'event_category': 'Powermail AJAX',
'event_label': formName,
'form_id': formUid,
'submission_type': 'ajax'
});
});
// Track form errors
jQuery(document).on('powermail:submit:error', function(e, data) {
gtag('event', 'form_error', {
'event_category': 'Powermail',
'event_label': 'Submission Error',
'form_id': data.formUid || 'unknown'
});
});
File Download Tracking (FAL)
Track downloads from TYPO3's File Abstraction Layer:
TypoScript Implementation
page.jsFooterInline.140 = TEXT
page.jsFooterInline.140.value (
// Track FAL file downloads
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href]').forEach(function(link) {
const href = link.getAttribute('href');
// Check for TYPO3 file links (fileadmin, _processed, etc.)
const isFileLink = href && (
href.includes('/fileadmin/') ||
href.includes('/_processed/') ||
/\.(pdf|zip|docx?|xlsx?|pptx?|txt|csv|mp4|mp3)$/i.test(href)
);
if (isFileLink) {
link.addEventListener('click', function(e) {
const fileName = href.split('/').pop().split('?')[0];
const fileExtension = fileName.split('.').pop().toLowerCase();
gtag('event', 'file_download', {
'event_category': 'Downloads',
'event_label': fileName,
'file_name': fileName,
'file_extension': fileExtension,
'link_url': href,
'link_text': this.textContent.trim()
});
});
}
});
});
)
FAL Metadata-Enhanced Tracking
# Create custom ViewHelper or DataProcessor to access FAL metadata
page = PAGE
page {
10 = FLUIDTEMPLATE
10 {
dataProcessing {
100 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
100 {
references.fieldName = media
as = files
}
}
}
}
In Fluid template:
<f:for each="{files}" as="file">
<a href="{file.publicUrl}"
data-file-type="{file.extension}"
data-file-size="{file.size}"
data-file-name="{file.name}"
class="tracked-download">
{file.title}
</a>
</f:for>
<script>
document.querySelectorAll('.tracked-download').forEach(function(link) {
link.addEventListener('click', function() {
gtag('event', 'file_download', {
'file_name': this.getAttribute('data-file-name'),
'file_extension': this.getAttribute('data-file-type'),
'file_size': this.getAttribute('data-file-size'),
'file_title': this.textContent.trim()
});
});
});
</script>
News Extension (EXT:news) Tracking
Track interactions with Georg Ringer's news extension:
Article View Tracking
[request.getQueryParams()['tx_news_pi1']['news'] > 0]
page.jsFooterInline.150 = TEXT
page.jsFooterInline.150.dataWrap (
gtag('event', 'view_item', {
'event_category': 'News',
'event_label': '{page:title}',
'item_id': '{request.getQueryParams()['tx_news_pi1']['news']}',
'content_type': 'article'
});
)
[END]
Category Tracking
[request.getQueryParams()['tx_news_pi1']['overwriteDemand']['categories'] != '']
page.jsFooterInline.151 = TEXT
page.jsFooterInline.151.dataWrap (
gtag('event', 'view_item_list', {
'event_category': 'News',
'event_label': 'Category View',
'item_list_name': 'news_category_{request.getQueryParams()['tx_news_pi1']['overwriteDemand']['categories']}'
});
)
[END]
Comment/Share Tracking via Fluid
<!-- In your News detail template -->
<f:if condition="{newsItem}">
<script>
// Track news article view
gtag('event', 'view_item', {
'content_type': 'news_article',
'item_id': '{newsItem.uid}',
'item_name': '{newsItem.title -> f:format.htmlentities()}',
'author': '{newsItem.author -> f:format.htmlentities()}',
'publish_date': '{newsItem.datetime -> f:format.date(format: "Y-m-d")}'
});
</script>
<!-- Track share button clicks -->
<div class="news-share">
<button class="share-facebook" data-article-id="{newsItem.uid}">Share on Facebook</button>
<button class="share-twitter" data-article-id="{newsItem.uid}">Share on Twitter</button>
</div>
<script>
document.querySelectorAll('.news-share button').forEach(function(button) {
button.addEventListener('click', function() {
const platform = this.classList.contains('share-facebook') ? 'facebook' : 'twitter';
gtag('event', 'share', {
'method': platform,
'content_type': 'news_article',
'item_id': this.getAttribute('data-article-id')
});
});
});
</script>
</f:if>
Custom Content Element Tracking
Accordion/Collapsible Tracking
page.jsFooterInline.160 = TEXT
page.jsFooterInline.160.value (
// Track accordion interactions
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.ce-accordion .accordion-toggle').forEach(function(toggle, index) {
toggle.addEventListener('click', function() {
const title = this.textContent.trim();
const isExpanding = !this.classList.contains('active');
gtag('event', isExpanding ? 'accordion_expand' : 'accordion_collapse', {
'event_category': 'Content Elements',
'event_label': title,
'accordion_position': index + 1
});
});
});
});
)
Tab Navigation Tracking
page.includeJSFooter.tabs = EXT:your_sitepackage/Resources/Public/JavaScript/tab-tracking.js
Resources/Public/JavaScript/tab-tracking.js:
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.ce-tabs .tab-navigation a').forEach(function(tabLink, index) {
tabLink.addEventListener('click', function(e) {
const tabName = this.textContent.trim();
gtag('event', 'tab_click', {
'event_category': 'Content Elements',
'event_label': tabName,
'tab_position': index + 1
});
});
});
});
User Authentication Tracking
Frontend User Login
# After successful login
[frontend.user.isLoggedIn == true]
page.jsFooterInline.170 = TEXT
page.jsFooterInline.170.value (
// Check if this is a new session
if (!sessionStorage.getItem('typo3_login_tracked')) {
gtag('event', 'login', {
'method': 'TYPO3 Frontend'
});
sessionStorage.setItem('typo3_login_tracked', '1');
}
)
[END]
User Registration Tracking
Create a custom Extbase controller action:
<?php
namespace Vendor\Extension\Controller;
class UserController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function createAction(\Vendor\Extension\Domain\Model\User $user)
{
$this->userRepository->add($user);
// Add GA4 tracking script
$GLOBALS['TSFE']->additionalFooterData['ga4_signup'] = '
<script>
gtag("event", "sign_up", {
"method": "TYPO3 Frontend Registration"
});
</script>';
$this->redirect('success');
}
}
Search Tracking (indexed_search)
Search Query Tracking
[request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword'] != '']
page.jsFooterInline.180 = TEXT
page.jsFooterInline.180.stdWrap {
dataWrap (
gtag('event', 'search', {
'search_term': '{request.getQueryParams()['tx_indexedsearch_pi2']['search']['sword']}',
'search_results': document.querySelectorAll('.tx-indexedsearch-res').length || 0
});
)
}
[END]
Search Result Clicks
page.jsFooterInline.181 = TEXT
page.jsFooterInline.181.value (
// Track search result clicks
document.querySelectorAll('.tx-indexedsearch-res a').forEach(function(link, index) {
link.addEventListener('click', function() {
const searchTerm = new URLSearchParams(window.location.search).get('tx_indexedsearch_pi2[search][sword]');
gtag('event', 'select_content', {
'content_type': 'search_result',
'item_id': index + 1,
'search_term': searchTerm || 'unknown'
});
});
});
)
Video Tracking (Custom Players)
Video.js Integration
page.includeJSFooter.videoTracking = EXT:your_sitepackage/Resources/Public/JavaScript/video-tracking.js
Resources/Public/JavaScript/video-tracking.js:
// Track custom video player events
document.addEventListener('DOMContentLoaded', function() {
const videos = document.querySelectorAll('video');
videos.forEach(function(video) {
const videoSrc = video.querySelector('source')?.src || video.currentSrc || 'unknown';
const videoTitle = video.getAttribute('title') || videoSrc.split('/').pop();
let tracked25 = false, tracked50 = false, tracked75 = false;
video.addEventListener('play', function() {
gtag('event', 'video_start', {
'video_title': videoTitle,
'video_url': videoSrc
});
});
video.addEventListener('timeupdate', function() {
const percent = (video.currentTime / video.duration) * 100;
if (percent >= 25 && !tracked25) {
tracked25 = true;
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_percent': 25
});
}
if (percent >= 50 && !tracked50) {
tracked50 = true;
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_percent': 50
});
}
if (percent >= 75 && !tracked75) {
tracked75 = true;
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_percent': 75
});
}
});
video.addEventListener('ended', function() {
gtag('event', 'video_complete', {
'video_title': videoTitle,
'video_url': videoSrc
});
});
});
});
Custom Button and CTA Tracking
TypoScript-Based Button Tracking
page.jsFooterInline.190 = TEXT
page.jsFooterInline.190.value (
// Track all buttons and CTAs
document.addEventListener('DOMContentLoaded', function() {
// Track Fluid-rendered buttons
document.querySelectorAll('.btn, .cta-button, .ce-button a').forEach(function(button) {
button.addEventListener('click', function() {
gtag('event', 'cta_click', {
'event_category': 'CTA',
'event_label': this.textContent.trim(),
'button_text': this.textContent.trim(),
'button_url': this.href || 'no-url',
'page_location': window.location.pathname
});
});
});
// Track newsletter signups
document.querySelectorAll('form[name="newsletter"] button[type="submit"]').forEach(function(button) {
button.addEventListener('click', function(e) {
gtag('event', 'newsletter_signup_attempt', {
'event_category': 'Engagement',
'event_label': 'Newsletter Form'
});
});
});
});
)
Advanced Event Tracking with Data Attributes
Fluid Template with Data Attributes
<f:link.page
pageUid="{page.uid}"
class="tracked-link"
data="{
gaCategory: 'Internal Navigation',
gaAction: 'click',
gaLabel: page.title
}"
>
{page.title}
</f:link.page>
Generic Data Attribute Tracker
page.jsFooterInline.200 = TEXT
page.jsFooterInline.200.value (
// Track all elements with data-ga-* attributes
document.querySelectorAll('[data-ga-category]').forEach(function(element) {
element.addEventListener('click', function() {
const category = this.getAttribute('data-ga-category');
const action = this.getAttribute('data-ga-action') || 'click';
const label = this.getAttribute('data-ga-label') || this.textContent.trim();
gtag('event', action, {
'event_category': category,
'event_label': label
});
});
});
)
User Properties (Custom Dimensions)
Set User Properties via TypoScript
[frontend.user.isLoggedIn == true]
page.jsFooterInline.210 = TEXT
page.jsFooterInline.210.dataWrap (
gtag('set', 'user_properties', {
'user_type': 'logged_in',
'user_group': '{TSFE:fe_user|user|usergroup}',
'member_since': 'frontend_user'
});
)
[END]
[frontend.user.isLoggedIn == false]
page.jsFooterInline.210 = TEXT
page.jsFooterInline.210.value (
gtag('set', 'user_properties', {
'user_type': 'guest'
});
)
[END]
Debugging Event Tracking
Enable GA4 Debug Mode
page.jsFooterInline.999 = TEXT
page.jsFooterInline.999.value (
// Enable debug mode in development
gtag('config', '{$analytics.ga4.measurementId}', {
'debug_mode': true
});
// Log all dataLayer pushes
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('dataLayer.push:', arguments);
return originalPush.apply(this, arguments);
};
)
TYPO3 Admin Panel Debug
Enable in Backend → User Settings → Admin Panel:
- Preview
- Cache (disable for testing)
- Info (see current TypoScript conditions)
Performance Considerations
Conditional Loading
# Only load event tracking scripts on specific pages
[page["uid"] in [10, 15, 20]]
page.includeJSFooter.eventTracking = EXT:your_sitepackage/Resources/Public/JavaScript/events.js
[END]
Defer Non-Critical Tracking
page.includeJSFooter {
ga4Events = EXT:your_sitepackage/Resources/Public/JavaScript/ga4-events.js
ga4Events.defer = 1
}
Next Steps
- Set Up E-commerce Tracking for TYPO3 shop extensions
- Debug Event Tracking Issues
- Implement GTM Data Layer for advanced tracking