This guide covers common issues specific to SilverStripe CMS sites. Issues often relate to caching, DataExtension conflicts, and versioned content tracking.
Common Issues Overview
SilverStripe CMS presents unique troubleshooting challenges due to its versioned content system, template inheritance, DataExtension architecture, and caching layers. This guide addresses platform-specific issues that developers and site administrators encounter.
Common Issue Categories
Performance Issues
- Template caching issues
- DataObject query optimization
- Elemental block rendering
- Static publishing conflicts
Tracking Issues
- CMS user exclusion not working
- Elemental block tracking gaps
- Versioned content analytics
- Subsite tracking conflicts
Installation Problems
Module Installation Issues
Composer Dependencies Failing
Symptoms:
- Module installation fails with version conflicts
- Composer can't resolve dependencies
- Memory limit exceeded during installation
Common causes:
# Version conflicts between modules
composer require vendor/module
# Error: Your requirements could not be resolved to an installable set of packages
Solutions:
- Check module compatibility:
# View SilverStripe version
vendor/bin/sake dev/build
# Check in output: SilverStripe Framework 5.x
# Ensure module supports your version
composer show vendor/module-name
- Increase memory limit:
# Temporary increase
php -d memory_limit=2G composer require vendor/module
# Permanent in php.ini
memory_limit = 2048M
- Update recipe first:
# Update core before adding modules
composer update silverstripe/recipe-cms
composer require vendor/new-module
dev/build Failures
Common error: "Class 'VendorName\ModuleName' not found"
Solutions:
# Regenerate autoloader
composer dump-autoload
# Force flush and rebuild
vendor/bin/sake dev/build flush=all
# Check for file permissions
chmod -R 755 vendor/
chmod -R 755 app/
Tracking Code Installation
Template Override Not Working
Problem: Added tracking code to template but not appearing on site
Check template precedence:
SilverStripe template search order:
1. themes/mytheme/templates/Layout/PageType.ss
2. themes/mytheme/templates/Layout/Page.ss
3. themes/mytheme/templates/Page.ss
4. vendor/module/templates/Page.ss
Solution:
<!-- In themes/mytheme/templates/Page.ss -->
<!DOCTYPE html>
<html>
<head>
<% base_tag %>
$MetaTags
<!-- Analytics tracking code -->
<% if not $Member %>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=$GoogleAnalyticsID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '$GoogleAnalyticsID');
</script>
<% end_if %>
</head>
Configuration Not Being Read
Problem: Config values not appearing in templates
Check YAML configuration:
# app/_config/analytics.yml
---
Name: appanalytics
After:
- '#coreconfig'
---
SilverStripe\SiteConfig\SiteConfig:
google_analytics_id: 'G-XXXXXXXXXX'
Access in template:
<!-- Correct -->
$SiteConfig.GoogleAnalyticsID
<!-- Wrong -->
$GoogleAnalyticsID
Flush configuration:
# Must flush config after YAML changes
vendor/bin/sake dev/build flush=all
Configuration Issues
| Issue | Symptoms | Common Causes | Solutions |
|---|---|---|---|
| Versioned Content Tracking | Draft pages showing analytics | Not checking isPublished | Add <% if $isPublished %> check in template |
| CMS User Tracking | Own visits in analytics | No member exclusion | Use <% if not $Member %> around tracking code |
| Elemental Block Events | Block interactions not tracked | Event handlers not bound | Add JavaScript after Elemental renders |
| Subsite Different Tracking IDs | Wrong GA property for subsites | Single tracking code | Implement per-subsite tracking logic |
| Static Caching Breaking Updates | Old tracking code served | Static publisher caching | Exclude tracking JS from static cache |
| DataExtension Conflicts | Extensions not applying | Extension order issues | Check extension priority in YAML |
| Template Caching | Changes not appearing | Partial cache enabled | Flush template cache or disable for dev |
| AJAX Page Loads | Virtual pageviews missing | History API not tracked | Implement history change listener |
| ModelAdmin Tracking | Backend actions tracked | No admin exclusion | Check for Security::getCurrentUser() |
| Fluent Multilingual | Wrong locale tracked | Locale not sent to GA | Add current locale to tracking data |
Debugging with Developer Tools
SilverStripe Debug Tools
Dev/Build Output Analysis
Check build output for errors:
vendor/bin/sake dev/build flush=all
# Look for:
# - Database was built
# - Class manifest built
# - Template manifest built
# - Any error messages or warnings
Common warnings:
WARNING: DataObject::get() called without specifying a class
NOTICE: Undefined index: FieldName
Template Debugging
Enable template debugging:
# app/_config.php (for development only)
use SilverStripe\View\SSViewer;
if (Director::isDev()) {
SSViewer::config()->set('source_file_comments', true);
}
Output:
<!-- template app/templates/Layout/Page.ss -->
<div class="content">
<!-- end template app/templates/Layout/Page.ss -->
Debug variable values:
<!-- In any .ss template -->
<% debug %>
Page variables:
$Title: {$Title}
$URLSegment: {$URLSegment}
$ClassName: {$ClassName}
$ID: {$ID}
$isPublished: {$isPublished}
$Member: {$Member}
<% end_debug %>
Query Debugging
Enable SQL query logging:
// app/_config.php (development only)
use SilverStripe\ORM\Connect\DatabaseException;
use SilverStripe\Dev\Debug;
if (Director::isDev()) {
global $databaseConfig;
$databaseConfig['database'] = DB::get_conn()
->getConnector()
->getSelectedDatabase();
}
Log queries in code:
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Dev\Debug;
// Debug DataList queries
$pages = Page::get()->filter('ShowInMenus', true);
Debug::dump($pages->sql());
Browser DevTools for SilverStripe
Check Template Output
View source to verify:
- Tracking code present in
<head> - Correct data attributes on elements
- No PHP errors outputting to HTML
Look for SilverStripe-specific markers:
<!-- SilverStripe template comments (if enabled) -->
<!-- template themes/mytheme/templates/Page.ss -->
<!-- Base tag required by SilverStripe -->
<base href="https://yoursite.com/">
JavaScript Console Errors
Common SilverStripe-related errors:
// jQuery conflict from CMS
Uncaught TypeError: $ is not a function
// Solution: Use jQuery() instead of $
jQuery(document).ready(function($) {
// Now $ is safe to use inside
});
Elemental block JavaScript:
// Check if Elemental loaded
if (typeof window.elemental !== 'undefined') {
console.log('Elemental blocks loaded');
}
// Track block renders
document.addEventListener('elemental.rendered', function(e) {
console.log('Block rendered:', e.detail);
});
Platform-Specific Challenges
Versioned Content System
Draft vs Published Pages
Problem: Analytics showing on draft pages visible only to CMS users
Solution - Check publication status:
<!-- In Page.ss template -->
<head>
<% base_tag %>
$MetaTags
<% if $isPublished %>
<% if not $Member %>
<!-- Only track published pages for non-members -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<% end_if %>
<% end_if %>
</head>
In PHP/Extension:
use SilverStripe\Security\Security;
use SilverStripe\Versioned\Versioned;
public function shouldTrackAnalytics()
{
// Don't track CMS users
if (Security::getCurrentUser()) {
return false;
}
// Only track published content
if ($this->isPublished()) {
return true;
}
return false;
}
Preview Mode Tracking
Problem: Preview links triggering analytics
Detect preview mode:
use SilverStripe\Versioned\Versioned;
public function IsPreviewMode()
{
return Versioned::get_stage() === Versioned::DRAFT;
}
Template usage:
<% if not $IsPreviewMode %>
<!-- Tracking code -->
<% end_if %>
Elemental Content Blocks
Block-Specific Tracking
Problem: Need to track interactions with individual Elemental blocks
DataExtension for blocks:
// app/src/Extensions/ElementTrackingExtension.php
namespace App\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\View\Requirements;
class ElementTrackingExtension extends Extension
{
public function onAfterInit()
{
Requirements::customScript(<<<JS
document.addEventListener('DOMContentLoaded', function() {
// Track all element interactions
document.querySelectorAll('[data-element-id]').forEach(function(element) {
element.addEventListener('click', function(e) {
if (typeof gtag === 'function') {
gtag('event', 'element_click', {
'element_id': element.dataset.elementId,
'element_type': element.dataset.elementType
});
}
});
});
});
JS);
}
}
YAML configuration:
# app/_config/extensions.yml
DNADesign\Elemental\Models\BaseElement:
extensions:
- App\Extensions\ElementTrackingExtension
Template output:
<!-- In ElementalArea.ss -->
<% loop $Elements %>
<div class="element $Type"
data-element-id="$ID"
data-element-type="$ClassName">
$forTemplate
</div>
<% end_loop %>
Subsites Module
Different Tracking IDs per Subsite
Problem: Need different Google Analytics properties for each subsite
PageExtension implementation:
// app/src/Extensions/SubsiteAnalyticsExtension.php
namespace App\Extensions;
use SilverStripe\Core\Extension;
use SilverStripe\Subsites\Model\Subsite;
class SubsiteAnalyticsExtension extends Extension
{
public function getGoogleAnalyticsID()
{
$trackingIDs = [
0 => 'G-AAAAAAAAAA', // Main site
1 => 'G-BBBBBBBBBB', // Subsite 1
2 => 'G-CCCCCCCCCC', // Subsite 2
];
$subsiteID = Subsite::currentSubsiteID() ?: 0;
return $trackingIDs[$subsiteID] ?? '';
}
}
YAML configuration:
# app/_config/subsite-analytics.yml
SilverStripe\CMS\Model\SiteTree:
extensions:
- App\Extensions\SubsiteAnalyticsExtension
Template usage:
<% if $GoogleAnalyticsID %>
<script async src="https://www.googletagmanager.com/gtag/js?id=$GoogleAnalyticsID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '$GoogleAnalyticsID');
</script>
<% end_if %>
Fluent Multilingual Module
Tracking Different Locales
Problem: Need to track which language version users view
Implementation:
// app/src/Extensions/FluentAnalyticsExtension.php
namespace App\Extensions;
use SilverStripe\Core\Extension;
use TractorCow\Fluent\State\FluentState;
class FluentAnalyticsExtension extends Extension
{
public function getCurrentLocale()
{
return FluentState::singleton()->getLocale();
}
public function getLocaleName()
{
$locale = $this->getCurrentLocale();
$locales = [
'en_US' => 'English',
'es_ES' => 'Spanish',
'fr_FR' => 'French',
];
return $locales[$locale] ?? $locale;
}
}
Template implementation:
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', {
'language': '$CurrentLocale',
'content_group': '$LocaleName'
});
</script>
Static Publisher Caching
Analytics Not Updating with Static Cache
Problem: Static publisher caching pages with old tracking code
Solution - Exclude from static cache:
// app/src/Extensions/StaticCacheExtension.php
namespace App\Extensions;
use SilverStripe\Core\Extension;
class StaticCacheExtension extends Extension
{
public function alterCachedPaths(&$paths)
{
// Don't cache pages with dynamic tracking
// Force these to be dynamic
$paths = array_filter($paths, function($path) {
// Exclude checkout, member areas, etc.
return !preg_match('#/(checkout|account|admin)#', $path);
});
}
}
Alternative - Dynamic includes:
# app/_config/staticpublisher.yml
SilverStripe\StaticPublishQueue\Publisher\StaticPublisher:
exclude_patterns:
- '/admin/*'
- '/Security/*'
- '/dev/*'
Use AJAX for dynamic content:
// Load tracking dynamically after page load
document.addEventListener('DOMContentLoaded', function() {
// Fetch current tracking code
fetch('/api/tracking-code')
.then(response => response.text())
.then(html => {
const div = document.createElement('div');
div.innerHTML = html;
document.head.appendChild(div);
});
});
Error Messages and Solutions
Common SilverStripe Errors
"Class does not exist"
Error:
ReflectionException: Class 'App\PageTypes\CustomPage' not found
Causes:
- File not in correct namespace
- Composer autoloader not regenerated
- Class name doesn't match filename
Solutions:
# Regenerate autoloader
composer dump-autoload
# Run dev/build
vendor/bin/sake dev/build flush=all
# Check file structure
app/
src/
PageTypes/
CustomPage.php # Must match class name
"Template not found"
Error:
Warning: Template "Layout/CustomPage" not found
Check template locations:
# Template must be in one of these locations:
themes/mytheme/templates/Layout/CustomPage.ss
themes/mytheme/templates/CustomPage.ss
app/templates/Layout/CustomPage.ss
app/templates/CustomPage.ss
Flush template cache:
vendor/bin/sake dev/build flush=all
# Or append ?flush=1 to URL
https://yoursite.com/?flush=1
"Database build failed"
Error during dev/build:
Couldn't run query: Table 'database.Page' doesn't exist
Solutions:
- Check database credentials:
// app/_config.php
use SilverStripe\Core\Environment;
// Ensure .env file has correct credentials:
// SS_DATABASE_NAME="database_name"
// SS_DATABASE_USERNAME="username"
// SS_DATABASE_PASSWORD="password"
- Drop and rebuild:
# WARNING: Destroys data!
# Only for development
# Drop all tables
vendor/bin/sake dev/tasks/DropAllTablesTask
# Rebuild
vendor/bin/sake dev/build
- Check permissions:
-- MySQL grants needed
GRANT ALL PRIVILEGES ON database_name.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
Tracking-Specific Errors
"Cannot modify header information"
Error:
Warning: Cannot modify header information - headers already sent by (output started at /path/to/template.ss:10)
Cause: Output before tracking redirects or cookies
Solution:
// Use HeaderField for custom headers
public function onAfterInit()
{
parent::onAfterInit();
// Set headers before any output
$this->getResponse()->addHeader('X-Tracking', 'enabled');
}
"gtag is not defined" in SilverStripe
Specific to SilverStripe Requirements system:
// Wrong - may load in wrong order
Requirements::javascript('https://www.googletagmanager.com/gtag/js?id=G-XXX');
Requirements::customScript('gtag("config", "G-XXX");');
// Correct - ensure proper loading
Requirements::insertHeadTags(<<<HTML
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
HTML
);
Performance Problems
Slow Template Rendering
Problem: Pages load slowly due to template complexity
Diagnosis:
// Enable template profiling
use SilverStripe\Dev\Debug;
$start = microtime(true);
$output = $this->renderWith('Page');
$duration = microtime(true) - $start;
Debug::log("Template render time: {$duration}s");
Solutions:
- Cache template includes:
<!-- Cache expensive includes -->
<% cached 'navigation', $LastEdited %>
<% include Navigation %>
<% end_cached %>
- Optimize DataList queries:
// Bad - N+1 query problem
<% loop $Children %>
$Title: <% loop $Children %>$Title<% end_loop %>
<% end_loop %>
// Good - Eager loading
public function getChildrenWithGrandchildren()
{
return $this->Children()->leftJoin('Children', 'Child.ID = Parent.ID');
}
Database Query Optimization
Problem: Too many queries slowing page load
Enable query counter:
// app/_config.php (development)
use SilverStripe\ORM\Connect\DatabaseException;
if (Director::isDev()) {
DB::query_count_enabled(true);
}
Check query count in template:
<!-- Bottom of Page.ss in dev mode -->
<% if $isDev %>
<div>Queries: {$queryCount}</div>
<% end_if %>
Optimize with:
// Use ::get() with specific filters
// Bad
$pages = Page::get();
foreach ($pages as $page) {
if ($page->ShowInMenus) {
// Use page
}
}
// Good
$pages = Page::get()->filter('ShowInMenus', true);
// Even better - limit fields
$pages = Page::get()
->filter('ShowInMenus', true)
->limit(10)
->setQueriedColumns(['ID', 'Title', 'URLSegment']);
When to Contact Support
SilverStripe Core Issues
Contact SilverStripe forum/support when:
- Core CMS functionality broken after update
- Database schema not building correctly
- Module compatibility issues between official modules
- Security vulnerabilities discovered
Where to get help:
- Official forum: https://forum.silverstripe.org/
- Stack Overflow: Tag
silverstripe - GitHub Issues: For specific module bugs
- Slack community: For quick questions
Module-Specific Support
Contact module maintainer when:
- Module not compatible with your SilverStripe version
- Module documentation unclear or outdated
- Features not working as described
- Bugs specific to the module
Check first:
# View module readme
cat vendor/vendor-name/module-name/README.md
# Check for known issues
# Visit GitHub repository issues page
When to Hire a SilverStripe Developer
Complex scenarios:
- Custom DataExtensions for analytics integration
- Elemental block development with tracking
- Subsites configuration with different tracking
- Performance optimization for high-traffic sites
- Custom module development for specialized tracking
- Migration from SilverStripe 3 to 4/5 with tracking preservation
- Headless SilverStripe with separate frontend
- Multi-site installations with centralized analytics
Finding developers:
- SilverStripe partners directory
- Forum members offering consulting
- Upwork/Freelancer with SilverStripe expertise
Advanced Troubleshooting
Debug Mode Configuration
Complete debug setup:
// app/_config.php
use SilverStripe\Control\Director;
use SilverStripe\Dev\Debug;
use SilverStripe\Core\Environment;
if (Director::isDev()) {
// Enable all debugging
ini_set('display_errors', 1);
error_reporting(E_ALL);
// SilverStripe debug
Debug::config()->set('friendly_error_page', false);
// Template debugging
SSViewer::config()->set('source_file_comments', true);
}
.env configuration:
# Environment type
SS_ENVIRONMENT_TYPE="dev"
# Database
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_SERVER="localhost"
SS_DATABASE_USERNAME="username"
SS_DATABASE_PASSWORD="password"
SS_DATABASE_NAME="database"
# Default admin
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
Log File Analysis
Check logs:
# SilverStripe logs
tail -f app/logs/error-*.log
# PHP error log
tail -f /var/log/php-errors.log
Custom logging:
use SilverStripe\Core\Injector\Injector;
use Psr\Log\LoggerInterface;
public function logAnalyticsEvent($event)
{
Injector::inst()->get(LoggerInterface::class)
->info("Analytics event: {$event}");
}
Related Global Guides
For platform-agnostic troubleshooting, see: