Silverstripe Troubleshooting: Common Issues and Fixes | OpsBlu Docs

Silverstripe Troubleshooting: Common Issues and Fixes

Common issues and solutions for SilverStripe CMS sites including caching, Elemental blocks, and tracking problems.

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:

  1. 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
  1. Increase memory limit:
# Temporary increase
php -d memory_limit=2G composer require vendor/module

# Permanent in php.ini
memory_limit = 2048M
  1. 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:

  1. File not in correct namespace
  2. Composer autoloader not regenerated
  3. 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:

  1. 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"
  1. Drop and rebuild:
# WARNING: Destroys data!
# Only for development

# Drop all tables
vendor/bin/sake dev/tasks/DropAllTablesTask

# Rebuild
vendor/bin/sake dev/build
  1. 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:

  1. Cache template includes:
<!-- Cache expensive includes -->
<% cached 'navigation', $LastEdited %>
    <% include Navigation %>
<% end_cached %>
  1. 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:

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:

  1. Custom DataExtensions for analytics integration
  2. Elemental block development with tracking
  3. Subsites configuration with different tracking
  4. Performance optimization for high-traffic sites
  5. Custom module development for specialized tracking
  6. Migration from SilverStripe 3 to 4/5 with tracking preservation
  7. Headless SilverStripe with separate frontend
  8. 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}");
}

For platform-agnostic troubleshooting, see: