Track user interactions on your SilverStripe site including form submissions, downloads, clicks, and custom events.
Prerequisites
- GA4 already installed on SilverStripe (setup guide)
- Understanding of SilverStripe templates and PHP
- Access to SilverStripe template files
Basic Event Tracking
JavaScript Event Tracking
File: themes/yourtheme/javascript/analytics.js
// Track custom event
function trackEvent(eventName, params) {
if (typeof gtag !== 'undefined') {
gtag('event', eventName, params);
}
}
// Export for use in templates
window.trackEvent = trackEvent;
Form Submission Tracking
Basic Form Tracking
File: app/src/PageController.php
<?php
namespace App;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\View\Requirements;
class PageController extends \PageController
{
private static $allowed_actions = [
'ContactForm',
];
public function ContactForm()
{
$form = Form::create(
$this,
'ContactForm',
FieldList::create(
TextField::create('Name'),
EmailField::create('Email'),
TextareaField::create('Message')
),
FieldList::create(
FormAction::create('doSubmitContact', 'Send')
),
RequiredFields::create(['Name', 'Email', 'Message'])
);
return $form;
}
public function doSubmitContact($data, $form)
{
// Process form data...
// Track successful submission
Requirements::customScript(<<<JS
if (typeof gtag !== 'undefined') {
gtag('event', 'form_submission', {
'form_name': 'contact',
'form_id': 'ContactForm'
});
}
JS
);
return $this->redirectBack()->with([
'Message' => 'Thank you for your submission!'
]);
}
}
Template-Based Form Tracking
File: themes/yourtheme/templates/Includes/ContactForm.ss
<form $FormAttributes 'contact')">
$Fields
$Actions
</form>
<script>
function trackFormSubmit(event, formName) {
if (typeof gtag !== 'undefined') {
gtag('event', 'form_submission', {
'form_name': formName,
'form_location': window.location.pathname
});
}
}
</script>
Download Tracking
Track File Downloads
File: themes/yourtheme/templates/Layout/Page.ss
<% loop $Content.Files %>
<a href="$URL" '{$Extension}', {$Size})"
download>
Download $Name
</a>
<% end_loop %>
<script>
function trackDownload(fileName, fileType, fileSize) {
if (typeof gtag !== 'undefined') {
gtag('event', 'file_download', {
'file_name': fileName,
'file_type': fileType,
'file_size': fileSize,
'link_url': event.target.href
});
}
}
</script>
PHP-Based Download Tracking
File: app/src/DownloadController.php
<?php
namespace App;
use SilverStripe\Control\Controller;
use SilverStripe\View\Requirements;
class DownloadController extends Controller
{
private static $url_segment = 'download';
private static $allowed_actions = [
'file',
];
public function file()
{
$fileID = $this->request->param('ID');
// Fetch file and serve...
// Track download server-side or via Requirements
Requirements::customScript(<<<JS
if (typeof gtag !== 'undefined') {
gtag('event', 'file_download', {
'file_id': '{$fileID}',
'download_method': 'controller'
});
}
JS
);
return $this->serveFile($file);
}
}
Click Tracking
External Link Tracking
File: themes/yourtheme/javascript/link-tracking.js
document.addEventListener('DOMContentLoaded', function() {
// Track all external links
document.querySelectorAll('a[href^="http"]').forEach(function(link) {
if (link.hostname !== window.location.hostname) {
link.addEventListener('click', function() {
if (typeof gtag !== 'undefined') {
gtag('event', 'click', {
'link_domain': link.hostname,
'link_url': link.href,
'outbound': true
});
}
});
}
});
});
Content Interaction Tracking
Blog Post Read Time
File: themes/yourtheme/templates/Layout/BlogPost.ss
<article data-post-id="$ID">
<h1>$Title</h1>
$Content
</article>
<script>
(function() {
var startTime = Date.now();
var tracked30s = false;
setTimeout(function() {
if (!tracked30s && typeof gtag !== 'undefined') {
gtag('event', 'page_engagement', {
'content_type': 'blog_post',
'content_id': '$ID',
'content_title': '$Title.JS',
'engagement_time': 30
});
tracked30s = true;
}
}, 30000);
})();
</script>
Search Tracking
File: app/src/SearchController.php
<?php
namespace App;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\View\Requirements;
class SearchController extends \PageController
{
public function index(HTTPRequest $request)
{
$query = $request->getVar('q');
$results = $this->performSearch($query);
// Track search
Requirements::customScript(<<<JS
if (typeof gtag !== 'undefined') {
gtag('event', 'search', {
'search_term': '{$query}',
'search_results': {$results->count()}
});
}
JS
);
return [
'Query' => $query,
'Results' => $results,
];
}
}
Video Tracking
File: themes/yourtheme/templates/Includes/VideoPlayer.ss
<video id="video-{$ID}" controls>
<source src="$VideoURL" type="video/mp4">
</video>
<script>
(function() {
var video = document.getElementById('video-{$ID}');
var tracked = {
play: false,
25: false,
50: false,
75: false,
complete: false
};
video.addEventListener('play', function() {
if (!tracked.play && typeof gtag !== 'undefined') {
gtag('event', 'video_start', {
'video_title': '$Title.JS',
'video_url': '$VideoURL'
});
tracked.play = true;
}
});
video.addEventListener('timeupdate', function() {
var percent = (video.currentTime / video.duration) * 100;
if (percent >= 25 && !tracked[25]) {
gtag('event', 'video_progress', { 'video_percent': 25 });
tracked[25] = true;
}
// Similar for 50, 75...
});
})();
</script>
Member-Specific Events
File: app/src/MemberExtension.php
<?php
namespace App;
use SilverStripe\ORM\DataExtension;
use SilverStripe\View\Requirements;
class MemberExtension extends DataExtension
{
public function onAfterLogin()
{
Requirements::customScript(<<<JS
if (typeof gtag !== 'undefined') {
gtag('event', 'login', {
'method': 'password',
'member_id': '{$this->owner->ID}'
});
}
JS
);
}
public function onAfterLogout()
{
Requirements::customScript(<<<JS
if (typeof gtag !== 'undefined') {
gtag('event', 'logout');
}
JS
);
}
}
Register extension:
# app/_config/config.yml
SilverStripe\Security\Member:
extensions:
- App\MemberExtension
Scroll Depth Tracking
File: themes/yourtheme/javascript/scroll-tracking.js
(function() {
var tracked = {
25: false,
50: false,
75: false,
100: false
};
window.addEventListener('scroll', function() {
var scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
Object.keys(tracked).forEach(function(milestone) {
if (scrollPercent >= milestone && !tracked[milestone]) {
if (typeof gtag !== 'undefined') {
gtag('event', 'scroll', {
'percent_scrolled': parseInt(milestone)
});
}
tracked[milestone] = true;
}
});
});
})();
Best Practices
- Use Consistent Event Names: Follow GA4 recommended events
- Include Context: Add relevant parameters to each event
- Avoid Over-Tracking: Don't track every minor interaction
- Test Events: Use DebugView to verify events before deployment