Version 4 supported
This version of Silverstripe CMS is still supported though will not receive any additional features. Go to documentation for the most recent stable version.

Upgrading a Silverstripe CMS 3 project to Silverstripe CMS 4

Silverstripe CMS applications should be kept up to date with the latest security releases. Usually an update or upgrade to your Silverstripe CMS installation means overwriting files, flushing the cache and updating your database schema.

Understanding and planning your upgrade

How easy will it be to update my project? It's a fair question, and sometimes a difficult one to answer.

  • Silverstripe CMS follows semantic versioning (see our release process for details).

    • "Major" releases introduce API changes that may break your application.
    • "Minor" releases (x.y) introduce API changes in a backwards compatible way and can mark some API as deprecated.
    • "Patch" releases (x.y.z) fix bugs without introducing any API changes.
  • If you've made custom branches of Silverstripe CMS core, or any thirdparty module, upgrades are going to be more complex.
  • More custom features will mean more work to re-test all of those features, and adapt to API changes in core.
  • Customisations of a well defined type - such as custom page types or custom blog widgets - are going to be easier to upgrade than customisations that modify deep system internals like rewriting SQL queries.

Overview of changes

There are some fundamental changes in Silverstripe CMS 4:

  • PHP 7.1 is now the minimum required version for Silverstripe CMS 4.5.0 and above. We strongly recommend you only use supported versions of PHP. Note: Silverstripe CMS 4.0.0 through 4.4.0 still support PHP 5.6.
  • PHP 7.4 is supported from Silverstripe CMS 4.5.3 and above.
  • Silverstripe CMS is now even more modular which allows you to remove functionality your project might not need.
  • Common functionality sets can now be installed via Silverstripe CMS recipes.
  • Silverstripe CMS modules can now be installed in the vendor/ folder along with your regular PHP packages.
  • All classes are namespaced: You have to use these, but can decide if you namespace your project code.
  • PHP traits replace a few core classes (e.g. Object) and make it easy to apply common patterns
  • Public files can now be served from a public/ webroot for added security.
  • Versioning is more powerful through an "ownership" concept, and available for all DataObject classes.
  • Changes across objects can be collected in a "campaign" for batch publication.
  • GraphQL is now the favourite way of creating web services with Silverstripe CMS.
  • Asset management has been completely redone with a brand new React-based UI, protected draft files and versioning.
  • Parts of the CMS UI are now built with React and Bootstrap instead of Entwine and custom CSS.
  • PSR-4 auto-loading is supported for modules and for your project code.

Dig into the changelogs for 4.0.0, 4.1.0, 4.2.0, and 4.3.0 to learn more about major API changes introduced by Silverstripe CMS 4.

Using recipes instead of requiring individual modules

The Silverstripe CMS and Silverstripe Framework are becoming more modular. Many of the secondary features contained in Silverstripe CMS 3 and Silverstripe Framework 3 have been moved to separate modules.

Silverstripe CMS 4 introduces the concept of recipes. Recipes are a combination of modules to achieve a common pattern.

Read the Switching to recipes section of this guide for more information about how recipes work.

Automating your upgrades using the Silverstripe CMS upgrader tool

We've developed an upgrader tool which you can use to help with the upgrade process. The upgrader is unlikely to completely upgrade your project, however it can take care of the most tedious part of the upgrade. It can also be used to upgrade your existing Silverstripe CMS 4 project to a newer minor release. If you have previously used the upgrader tool, make sure to use its newest version. Since 1.4.0, there's a self update feature.

Identify known issues

It's sometimes hard to tell if an issue you're hitting is caused by upgrading project code, or is a known issue in Silverstripe CMS. Often an issue is fixed already, but not yet packaged in the release you're upgrading to. Use our issue search across our core modules, and ensure you're including closed ones (they might be done but not released yet). If you're unsure, report a bug.

Step 0 - pre-requisites and background work

Before you begin the upgrade process, make sure you meet these pre-requisites.

Back up your files and database

  • Set up your codebase in your development environment.
  • Backup your database content.
  • Backup your codebase (use version control if possible).

Never update a website on the live server. Get it working on a development copy first!

Install composer

Composer is a tool for managing PHP dependencies. Silverstripe CMS 4 requires composer version 1.1 or greater. Read the Silverstripe CMS Getting started guide for more details.

We recommend using recipe-cms in your composer.json file to help you keep up to date.

    "require": {
        "silverstripe/recipe-cms": "^4"

Running composer update will install additional dependencies, such as the admin, asset-admin, reports, errorpage, and siteconfig modules.

If you want more granular control over what gets installed, check out the recipe plugin repository as well as the composer.json files in recipe-core and recipe-cms.

For a description on how to handle issues with pre-existing composer installs or upgrading other modules, read through the Step 1 - Upgrade your dependencies section.

Install or update the upgrader tool

Using the upgrader is not mandatory, but it can speed up the process.

The upgrader is available as a phar executable.

To install the PHAR executable:

  1. Download the upgrader as a PHAR executable or wget
  2. Make the file executable chmod +x upgrade-code.phar
  3. Move the file to a folder in your path, for example sudo mv upgrade-code.phar /usr/local/bin/upgrade-code

When starting a new upgrade project, it's a good idea to check if you are using the latest release of the upgrader. Releases from 1.4 and above ship with a self-update command and will warn you if you are using an outdated version. If you are upgrading from a prior version, follow the regular installation instructions and override your existing executable.

You can run upgrade-code help to get more information about the upgrader or upgrade-code help command-name to information about a specific command.

Each command in the upgrader has somewhat different arguments. However, most of them accept these two options:

  • --write which tells the upgrader to apply changes to your code base
  • --root-dir which can be use to explicitly specify the root of your project. If this is not specified then the current working directory is assumed to be the root of the project.

Sample upgrader commands in this guide assume your working directory is the root of your Silverstripe CMS project. You'll need to use the --root-dir flag if that's not the case.

Install the upgrader globally with composer

You can install the upgrader globally with composer. This can make it easier to update to newer releases, however you can get dependency conflicts if you have other packages installed globally.

To install the upgrader globally run this command.

composer global require silverstripe/upgrader

Add your global composer bin directory to your path. On *nix system, this directory is normally located at $HOME/.composer/vendor/bin. On Windows system, this directory is normally located at C:\Users\<COMPUTER NAME>\AppData\Roaming\Composer\vendor\bin. You can find the exact location by running this command:

composer global config bin-dir

On *nix system, the following command will add your global composer bin directory to your path if bash is your default shell environment:

echo 'export PATH=$PATH:~/.composer/vendor/bin/' >> ~/.bash_profile
source ~/.bash_profile

Running all the upgrader commands in this guide with one line

The upgrader comes with an all command. This command will attempt to run all the upgrader commands in the same order as this guide. This is unlikely to work on your first try, but can be a good way to get started without going through this entire guide.

upgrade-code all --namespace="App\\Web" --psr4
  • --recipe-core-constraint defines your Silverstripe CMS release version (optional, will default to the most recent stable release).
  • --cwp-constraint can be used instead --recipe-core-constraint when upgrading a CWP project.
  • --namespace allows you to specify how your project will be namespaced (optional).
  • --psr4 allows you to specify that your project structure respects the PSR-4 standard and to automatically use sub-namespaces.
  • --skip-add-namespace allows you to skip the add-namespace command.
  • --skip-reorganise allows you to skip the reorganise command.
  • --skip-webroot allows you to skip the webroot command.

Branching your project

Creating a dedicated branch in your source version control system to track your upgrade work can help you manage your upgrade. If you're upgrading a big project, you should consider creating individual branches or commits for each step.

Step 1 - upgrade your dependencies

The first step is to update your dependencies' constraints in your composer.json file to require the latest version of modules.

Automatically upgrade dependencies with the recompose upgrader command

If you've installed the upgrader, you can use the recompose command to help you upgrade your dependencies. This command will try to:

  • upgrade your PHP constraint
  • upgrade core Silverstripe CMS modules to their version 4 equivalent
  • switch to recipes where possible
  • find Silverstripe CMS 4 compatible versions of third party modules.

Take for example the following Silverstripe CMS 3 composer.json file.

    "name": "app/cms-website",
    "description": "The Example website project.",
    "license": "BSD-3",
    "require": {
        "php": ">=5.3.3",
        "silverstripe/cms": "3.6.5@stable",
        "silverstripe/framework": "3.6.5@stable",
        "silverstripe/reports": "3.6.5@stable",
        "silverstripe/siteconfig": "3.6.5@stable",
        "dnadesign/silverstripe-elemental": "^1.8.0"

You can upgrade the composer.json file with this command:

upgrade-code recompose --write

You can add a --recipe-core-constraint flag to target a specific version of silverstripe/recipe-core. By default, the project will be upgraded to the latest stable version. If you are upgrading a CWP project, you can use --cwp-constraint instead to target a specific version of cwp/cwp-core.

The upgrader uses caret version constraint by default. This will cause composer to install compatible minor releases. You can use the --strict option if you want to use the more conservative tilde version constraints.

Omit the --write flag to preview your changes.

Your upgraded composer.json file will look like this.

    "name": "app/cms-website",
    "description": "The Example website project.",
    "license": "BSD-3",
    "require": {
        "dnadesign/silverstripe-elemental": "^4.0",
        "php": ">=5.6",
        "silverstripe/recipe-cms": "^4.3"

If the recompose command can't find a compatible version for one of your modules, it will keep this dependency in your composer.json file with its existing constraint.

Continue to Step 2

Manually upgrading your dependencies

The instructions in this section assume that you'll be editing your composer.json file in a text editor.

Switching to recipes

Where possible, we recommend you use recipes.

If your Silverstripe CMS 3 project requires the silverstripe/cms module, replace that dependency with silverstripe/recipe-cms. The version constraint for silverstripe/recipe-cms must match your targeted version of Silverstripe CMS:

  • ~4.0.0 to upgrade to Silverstripe CMS 4.0
  • ~4.1.0 to upgrade to Silverstripe CMS 4.1
  • ~4.2.0 to upgrade to Silverstripe CMS 4.2
  • and so on.

If your Silverstripe CMS 3 project requires the silverstripe/framework module without silverstripe/cms, replace silverstripe/framework with silverstripe/recipe-core. The version constraint for silverstripe/recipe-core must match your targeted version of Silverstripe CMS:

  • ~4.0.0 to upgrade to Silverstripe CMS 4.0
  • ~4.1.0 to upgrade to Silverstripe CMS 4.1
  • ~4.2.0 to upgrade to Silverstripe CMS 4.2
  • and so on.

The following modules are implicitly required by silverstripe/recipe-core. They can be removed from your composer.json dependencies if you are using silverstripe/recipe-core or silverstripe/recipe-cms.

  • silverstripe/framework
  • silverstripe/config
  • silverstripe/assets

The following modules are implicitly required by silverstripe/recipe-cms. They can be removed from your composer.json dependencies if you are using silverstripe/recipe-cms.

  • silverstripe/admin
  • silverstripe/asset-admin
  • silverstripe/campaign-admin
  • silverstripe/cms
  • silverstripe/errorpage
  • silverstripe/reports
  • silverstripe/graphql
  • silverstripe/siteconfig
  • silverstripe/versioned
  • silverstripe/recipe-core

Take for example the following Silverstripe CMS 3 composer.json.

    "name": "app/cms-website",
    "require": {
        "silverstripe/cms": "3.6.5@stable",
        "silverstripe/framework": "3.6.5@stable",
        "silverstripe/reports": "3.6.5@stable",
        "silverstripe/siteconfig": "3.6.5@stable"

After switching to Silverstripe CMS 4 recipes, the composer.json file should look like this.

    "name": "app/cms-website",
    "require": {
        "silverstripe/recipe-cms": "~4.1.0"

Explicitly defining your dependencies

If you would rather explicitly define your dependencies, you can do so. Update the silverstripe/framework constraint and silverstripe/cms constraint to match your targeted minor version of Silverstripe CMS 4. If you use silverstripe/reports and silverstripe/siteconfig, update their constraints as well.

In most cases, you'll also want to require the same modules as the equivalent recipes. If you don't, your users will likely lose some features after the upgrade is completed.

Take for example the following Silverstripe CMS 3 composer.json.

    "name": "app/cms-website",
    "require": {
        "silverstripe/cms": "3.6.5@stable",
        "silverstripe/framework": "3.6.5@stable",
        "silverstripe/reports": "3.6.5@stable",
        "silverstripe/siteconfig": "3.6.5@stable"

After switching to Silverstripe CMS 4 and explicitly defining your dependencies, the composer.json file should look like this.

    "name": "app/cms-website",
    "require": {
         "silverstripe/cms": "~4.1.0",
         "silverstripe/framework": "~4.1.0",
         "silverstripe/reports": "~4.1.0",
         "silverstripe/siteconfig": "~4.1.0",
         "silverstripe/admin": "~1.1.0",
         "silverstripe/asset-admin": "~1.1.0",
         "silverstripe/campaign-admin": "~1.1.0",
         "silverstripe/errorpage": "~1.1.0",
         "silverstripe/graphql": "~1.1.0",
         "silverstripe/versioned": "~1.1.0"

Updating third party dependencies

If you project requires third party modules, you'll need to adjust their associated constraint. This will allow you to retrieve a Silverstripe CMS 4 compatible version of the module.

Look up the module on Packagist to see if a Silverstripe CMS 4 version is provided.

Take for example the following Silverstripe CMS 3 composer.json.

    "name": "app/cms-website",
    "require": {
        "silverstripe/framework": "3.6.5@stable",
        "silverstripe/cms": "3.6.5@stable",
        "dnadesign/silverstripe-elemental": "^1.8.0"

Looking at the Packagist entry for dnadesign/silverstripe-elemental, you can see that versions 2.0.0 and above of this module are compatible with Silverstripe CMS 4. So you can update that constraint to ^2.0.0.

Alternatively, you can set a very permissive constraint and let composer find a Silverstripe CMS 4 compatible version. After you're done updating your dependencies, make sure you adjust your constraints to be more specific.

Once you've updated your third-party modules constraints, try updating your dependencies by running composer update. If composer can't resolve all your dependencies it will throw an error.

Resolving conflicts

You'll likely have some conflicts to resolve, whether you've updated your dependencies with the upgrader or manually.

Running a composer update will tell you which modules are conflicted and suggest alternative combinations of modules that might work.

The most typical reason for a conflict is that the maintainer of a module has not released a version compatible with Silverstripe CMS 4.

If the maintainer of the module is in the process of upgrading to Silverstripe CMS 4, a development version of the module might be available. In some cases, it can be worthwhile to look up the repository of the module or to reach out to the maintainer.

Another possible cause of a dependency conflict is the use of private packages. The recompose command does not take into consideration the repositories entries in your project's composer.json file. Constraints on private packages have to be defined manually.

Read the Composer Repositories documentation for more information on private repositories.

If you're going to install a development version of third party modules, you should consider adding the following entries to your composer.json file:

  // ...
  "minimum-stability": "dev",
  "prefer-stable": true
  // ...

If no development release is available for Silverstripe CMS 4, you can upgrade the module manually or remove the module from your project.

Upgrading the module manually

To upgrade an incompatible module yourself, you can try the options below.

Fork the affected module and upgrade it yourself

This approach has the advantage of keeping the module out of your codebase. It also makes it easy to reuse the code afterwards. This requires you to track the code in a separate repository.

When forking the module, you should convert it to a vendor module.

Upgrade the module so it works with version 4 of Silverstripe CMS, commit and push your changes to your forked repository.

See Upgrading a module for more information on how to upgrade a Silverstripe CMS module.

If you're taking the time to upgrade a third party module, consider doing a pull request against the original project so other developers can benefit from your work. Or you can release your fork as a separate module.

If you want to keep your fork private, you can include it in your project by adding a vcs repository entry in your composer file:

    "repositories": [
            "type": "vcs",
            "url": ""

Learn about how to publish a Silverstripe CMS module

Integrate the affected module into your project's codebase

You can add the module codebase to your own project. This is the simplest option, but it increases the complexity of your project, and the amount of code you have to maintain, therefore it is discouraged.

If you choose this option, the module will be treated as a root module, which is discouraged in Silverstripe CMS 4.

  1. Remove the module from your dependencies by manually editing your composer.json file. Do not use composer remove as this will remove your folder.
  2. Update your .gitignore file to track the module.
  3. Remove the composer.json from the module.

Note that all commands that need to be applied to mysite will also need to be applied to any root modules you are tracking in your project.

Removing the module from your project

You can remove the module completely if you do not need it.

This can be done simply by removing the dependency: composer remove <package>

Finalising your dependency upgrade

Once you've resolved all conflicts in your composer.json file, composer update will be able to run without errors.

This will install your new dependencies. You'll notice many of the folders in the root of your project will disappear. That's because Silverstripe CMS 4 modules can be installed in the vendor folder like generic PHP packages.

If you've decided to use recipes, some generic files will be copied from the recipe into your project. The extra attribute in your composer.json file will be updated to keep track of those new files.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 2 - update your environment configuration

The PHP configuration _ss_environment.php file has been replaced with a non-executable .env file. It follows a syntax similar to a .ini file for key/value pair assignment. Your .env file may be placed in your project root, or one level above your project root.

Read the Environment management documentation to learn more about configuring your project's environment.

Automatically convert _ss_environment.php to .env

If you have installed the upgrader tool, you can use the environment command to generate a valid .env file from your existing _ss_environment.php file.

upgrade-code environment --write

If your _ss_environment.php file contains unusual logic (conditional statements or loops), you will get a warning. upgrade-code will still try to convert the file, but you should double-check the output. Omit the --write flag to do a dry-run.

Continue to "Cleaning up mysite/_config.php"

Manually convert _ss_environment.php to .env

Create a .env file in the root of your project. Replace define statements from _ss_environment.php with KEY=VALUE pairs in .env.

Most Silverstripe CMS 3 environment variables have been carried over to Silverstripe CMS 4. See Environment Management docs for the full list of available variables. Your .env file can contain environment variables specific to your project as well.

The global array $_FILE_TO_URL_MAPPING has been removed and replaced with the SS_BASE_URL environment variable. SS_BASE_URL expects an absolute URL with an optional protocol. The following are values would be valid entries for SS_BASE_URL:

  • http://localhost/
  • https://localhost/
  • //localhost/

For example, take the following _ss_environment.php file.

// Environment
define('SS_ENVIRONMENT_TYPE', 'dev');
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
$_FILE_TO_URL_MAPPING[__DIR__] = 'http://localhost';

// Database
define('SS_DATABASE_CHOOSE_NAME', true);
define('SS_DATABASE_CLASS', 'MySQLDatabase');
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_SERVER', '');

The equivalent .env file will look like this.

## Environment

## Database

Cleaning up mysite/_config.php after your environment configuration upgrade

Regardless if you've used the automated or manual path, you'll need to clean up your mysite/_config.php file after upgrading your environment file.

The global values $database and $databaseConfig have been deprecated. Your database configuration details should be stored in your .env file. If you want to keep your database configuration in _config.php, you can use the new DB::setConfig() API, however this is discouraged.

Requiring conf/ConfigureFromEnv.php is is no longer necessary. You should remove any references to it in _config.php.

The removal of the _ss_environment.php file means that conditional logic is no longer available in the environment variable set-up process. This encouraged bad practice and should be avoided. If you still require conditional logic early in the bootstrap, this is best placed in the _config.php files.

To access environment variables, use the SilverStripe\Core\Environment::getEnv() method. To define environment variables, use the SilverStripe\Core\Environment::setEnv() method.

Finalising your environment upgrade

It's inadvisable to track your .env file in your source control system as it might contain sensitive information.

You should ignore the .env file by adding an entry to your .gitignore file. You can create a sample environment configuration by duplicating your .env file as .env.sample, and removing sensitive information from it. You can safely delete your legacy _ss_environment.php if you want.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 3 - namespacing your project (optional)

Namespacing your code is an optional step. It is recommended and will help future-proof your code base. Read more about PHP Namespaces and the PSR-4 Autoloader Standard.

Before you start namespacing your codebase

You need to choose a root namespace for your project. We recommend following the Vendor\Package pattern. The Page and PageController classes must be defined in the global namespace (or without a namespace).

If you want your codebase to comply with the PSR-4 standard, make sure sub-directories of your code folder are using the UpperCamelCase naming convention. For example, mysite/code/page_types should be renamed to mysite/code/PageTypes.

Automatically namespacing your codebase with the upgrader

The add-namespace command of the upgrader tool provides a feature to namespace your codebase and to automatic update references to those classes.

upgrade-code add-namespace "App\\Web" ./mysite/code --recursive --write

This task will do the following:

  • Add the given namespace to all files in the code folder, and subdirectories
  • All references to classes in any namespaced files will be safely retained with additional use directives added as necessary
  • A mysite/.upgrade.yml file will be created/updated to record the new fully qualified name of each class. This will be used in later steps to update references to the outdated class names in your own project code.

By default, the same namespace will be applied to all your classes regardless of which directory they are in. If you want to apply different namespaces to different folders to be compliant with PSR-4, combine the --recursive option with the --psr4 option. Your folder structure must be PSR-4 compliant for this to work. If you want to do a dry-run, omit the --write option to see a preview of all changed project files.

Continue to Step 4

Manually namespacing your codebase

Go through each PHP file under mysite/code and add a namespace statement at the top, with the exception of the files for Page or PageController.

Take for example this Silverstripe CMS 3 file located at mysite/code/Products/ExplosiveTennisBall.php.

namespace App\Web\Products;

class ExplosiveTennisBall extends DataObject
    // ...

Assuming your root namespace is App\Web, the equivalent namespaced file will look like this.

namespace App\Web\Products;

class ExplosiveTennisBall extends DataObject
    // ...

If you intend to use the upgrader to update references to your namespaced classes, you'll need to create a mysite/.upgrade.yml file.

  ExplosiveTennisBall: App\Web\Products\ExplosiveTennisBall

If you intend to manually update references to your namespaced classes, you'll need to go through each of your file to add use statements.

For example, if mysite/code/ProductService.php is using the ExplosiveTennisBall class, you'll need to add a use statement at the top of the file just after it's own namespace definition.

namespace App\Web;

class ProductService
    // ...

Enable PSR-4 auto-loading in your composer.json file

If you have namespaced your project and followed the PSR-4 convention, you have the option to enable PSR-4 auto-loading in your composer.json file. Enabling PSR-4 auto-loading is optional. It will provide better auto-loading of your classes in your development environment and will future proof your code.

For example, let's say you have defined the following namespaces for the following folders:

  • App\Web for your main application logic contained in mysite/code
  • App\SubModule for a secondary module contained in sub-module/code
  • App\Web\Tests for your application test suite contained in mysite/tests.

Your autoload section in your composer.json file should look like this:

    // ...
    "autoload": {
        "psr-4": {
            "App\\Web\\": "mysite/code",
            "App\\SubModule\\": "sub-module/code"
    "autoload-dev": {
        "psr-4": {
            "App\\Web\\Tests\\": "mysite/tests"
    // ...

Read the Composer schema autoload documentation for more information about configuring auto-loading in your project.

Finalise your project namespacing

All your classes should now be fully namespaced.

Note that applying a namespace to your project will also affect which template file Silverstripe CMS tries to load when rendering certain objects.

For example, pretend you have a RoadRunnerPage class that extends Page. In Silverstripe CMS 3, you would define a template for this page in themes/example/templates/Layout/ If you decide to move RoadRunnerPage to App\Web\RoadRunnerPage, you'll need to move the template to themes/example/templates/App/Web/Layout/

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 4 - update codebase with references to newly namespaced classes

All core PHP classes in Silverstripe CMS 4 have been namespaced. For example, DataObject is now called SilverStripe\ORM\DataObject. Your project codebase, config files and language files need be updated to reference those newly namespaced classes. This will include explicit references in your PHP code, but also string that contain the name of a class. If you've opted to namespace your own code in the previous step, those references will need to be updated as well.

Automatically update namespaced references with the upgrade command

If you've installed the upgrader, you can use the upgrade command to update references to namespaced classes.

The upgrade command will update PHP files, YAML configuration files, and YAML language files.

Each core Silverstripe CMS 4 module includes a .upgrade.yml that defines the equivalent fully qualified name of each class. Most third party Silverstripe CMS modules that have been upgraded to be compatible with Silverstripe CMS 4, also include a .upgrade.yml. If you've namespaced your own project, you'll need to provide your own .upgrade.yml file . If you've used the upgrader to namespace your project, that file will have been created for you.

The upgrade command will try to update some strings that reference the old name of some classes. In some cases this might not be what you want. You can tell the upgrader to skip specific strings by using the @skipUpgrade flag in your PHPDoc comment. For example:

/** @skipUpgrade */
return Injector::inst()->get('ProductService');

Execute the upgrade command with this command.

upgrade-code upgrade ./mysite/ --write

If you omit the --write flag you will get a preview of what change the upgrader will apply to your codebase. This can be helpful if you are tweaking your .upgrade.yml or if you are trying to identify areas where you should add a @skipUpgrade statement,

You can also tweak which rules to apply with the --rule flag: code, config, and lang. For example, the following command will only upgrade lang and config files:

upgrade-code upgrade ./mysite/ --rule=config --rule=lang

The upgrade command can alter big chunks of your codebase. While it works reasonably well in most use cases, you should not trust it blindly. You should take time to review all changes applied by the upgrade command and confirm you are happy with them.

Continue to "Finalising namespace updates"

Rename warnings

You can also show extra warnings for potentially ambiguous mappings with the renameWarnings property:

  - File
  - Image

An example of an ambiguous rename would be:

namespace App\Web\Products;

class ExplosiveTennisBall extends DataObject
    private static $has_one = [
        'Image' => 'Image',
    // ...

Add the --prompt flag to manually approve ambiguous class renames.

Manually update namespaced references

If you decide to update your namespace references by hand, you'll need to go through the entire code base and update them all from the old non-namespaced Silverstripe CMS classes to the new namespaced equivalent. If you are referencing classes from third party modules that have been namespaced, you'll need to update those as well.

Update explicit references to classes in your code

Wherever your code explicitly references a Silverstripe CMS class, it will need to be updated to the new namespaced equivalent. You can either update the reference to use the fully qualified name of the class or you can add a use statement to your file.

For example take the following Silverstripe CMS 3 class. DataObject and FieldList need to point to their namespace equivalents.

namespace App\Web\Products;

class ExplosiveTennisBall extends DataObject
    public function getCMSFields()
           return FieldList::create([]);

You can add use statements at the top of your file to reference the fully qualified name of DataObject and FieldList.

namespace App\Web\Products;

use SilverStripe\ORM\DataObject;

class ExplosiveTennisBall extends DataObject
  // ...

Alternatively, you can update the references to the fully qualified names.

namespace App\Web\Products;

class ExplosiveTennisBall extends SilverStripe\ORM\DataObject
    public function getCMSFields()
           return SilverStripe\Forms\FieldList::create([]);

Update string references to classes

In many cases, Silverstripe CMS expects to be provided the name of a class as a string. Typical scenarios include:

  • defining an has_one or has_many relationship on a DataObject
  • requesting an instance of class via the Injector
  • specifying managed models for a ModelAdmin.

Those string need to use the fully qualified name of their matching classes. Take for example the following class.

namespace App\Web\Products;

use SilverStripe\ORM\DataObject;

class ExplosiveTennisBall extends DataObject
    private static $has_one = [
        'Thumbnail' => 'Image',

    private static $has_many = [
        'Tags' => 'BlogPost',

    public function getShippingCost()
        return Injector::inst('ProductService')->calculateCost($this);

Image, BlogPost, and ProductService represent classes. Those strings need to be updated to specify the full namespace.

The best way of achieving this is to use the ::class PHP magic class constant which will return the fully qualified name of a class.

Our example could be update to:

namespace App\Web\Products;

use App\Web\ProductService;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\ORM\DataObject;

class ExplosiveTennisBall extends DataObject
    private static $has_one = [
        'Thumbnail' => Image::class,

    private static $has_many = [
        'Tags' => BlogPost::class,

    public function getShippingCost()
        return Injector::inst(ProductService::class)->calculateCost($this);

Alternatively, you can spell out the full qualified name of each class in a string. For example, 'Image' would become 'SilverStripe\\Assets\\Image'. Note the use of the double backslash — this is necessary because the backslash is an escape character.

Update references to classes in your YAML config

YAML configuration files can reference Silverstripe CMS classes. Those references also need to use the fully qualified name of each class.

Take for example the following Silverstripe CMS 3 YAML configuration file.

      RoadRunnerSpeed: 99999999
      CoyoteSpeed: 1

    - HasOneExplosiveTennisBallExtension


In Silverstripe CMS 4, this will become:

      RoadRunnerSpeed: 99999999
      CoyoteSpeed: 1

    - App\Web\Extensions\HasOneExplosiveTennisBallExtension


Update references to classes in your language files

Translation keys are normally tied to classes. If you override Silverstripe CMS's default translation or if you are localising your own project, you'll need to update those references to use the fully qualified name of each class.

For example, let's say you had the following translation file in mysite/lang/eng.yml.

    SALUTATION: Beep Beep

In Silverstripe CMS 4, it would become:

    SALUTATION: Beep Beep

Finalising namespace updates

You'll need to perform the following steps manually, even if you've used the automated rewrite of namespaces.

DataObject database tables will default to use a namespaced name. For example, if you have a class under App\Web\Products\ExplosiveTennisBall that extends DataObject, the matching table in your database will be called App_Web_Products_ExplosiveTennisBall. You can define a private static $table_name property on your DataObjects to use more convenient table names. For example, private static $table_name = 'ExplosiveTennisBall';.

In your PHP code, calls to the _t() method should be updated to use the full namespace of the target class.

# Old Silverstripe CMS 3 way
$translation = _t('CMSMain.ACCESS', "Access to ''{title}'' section", ['title' => 'Products']);

# New Silverstripe CMS 4
use SilverStripe\CMS\Controllers\CMSMain;
// ...
$translation = _t(CMSMain::class . '.ACCESS', "Access to '{title}' section", ['title' => 'Products']);

If you're calling _t() to retrieve a translation for the current class, you can also use __CLASS__ or self::class. For example:

namespace App\Web\Services;

class ProductService
    public function getTranslation()
        # Those two lines are equivalent.
        $translation = _t(__CLASS__ . '.PRODUCT', 'Product');
        $translation = _t(self::class . '.PRODUCT', 'Product');
        return $translation;

Avoid using static::class or parent::class to retrieve translated string. It will retrieve unpredictable values bases on the class inheritance.

If your template files contain translatable strings, they also need to be updated to referenced the namespaced classes. For example, <%t Member.SINGULARNAME 'Member' %> would become <%t SilverStripe\Security\Member.SINGULARNAME 'Member' %>.

Your codebase should now be referencing valid Silverstripe CMS 4 classes. This means that your classes can be loaded at runtime. However, your codebase will still be using an outdated API. This is a good point to commit your changes to your source control system before moving on to the next step.

Step 5 - updating your codebase to use Silverstripe CMS 4 API

This is the most intricate and potentially time-consuming part of the upgrade. It involves going through your entire codebase to remove references to deprecated APIs and update your project logic.

Automatically update deprecated API references with the inspect command

The upgrader has an inspect command that can flag deprecated API usage, and in some cases, update your codebase to the Silverstripe CMS 4 equivalent. This does require you to carefully review each change and warning to make sure the updated logic still work as intended. Even so, it is a huge time-saver compared to reviewing your code base manually.

Note that the inspect command loads your files with PHP interpreter. So basic syntax errors — for example, extending a class that does not exists — will cause an immediate failure. For this reason, you need to complete Step 4 - Update codebase with references to newly namespaced classes before running the inspect command.

upgrade-code inspect ./mysite/ --write

You can omit the --write flag if you just want to view the proposed changes without applying them. You can run the command on a specific subdirectory or file. This can be more manageable if you have a big project to go through.

Like the upgrade command, inspect gets its list of API changes from .upgrade.yml files. So you may get upgrade suggestions and substitution from third party modules. You can even include your own project specific changes in your .upgrade.yml if you want.

Sample output of the inspect command

Here's some sample output of what you might get back from the inspect command.

upgrade-code inspect ./mysite/Models/Coyote.php

Running post-upgrade on "/var/www/SS_example/mysite/code/Models/Coyote.php"
[2018-06-06 13:35:38] Applying ApiChangeWarningsRule to Coyote.php...
modified:  Coyote.php
@@ -68,7 +68,7 @@
         // Getting a reference to Coyote's list of crazy ideas
-        $manyManyRelation = $this->manyManyComponent('CrazyIdeas');
+        $manyManyRelation = $this->getSchema()->manyManyComponent('CrazyIdeas');
         return $manyManyRelation;

Warnings for Coyote.php:
 - Coyote.php:20 SS_Cache: Using symfony/cache now (
 - Coyote.php:42 SilverStripe\Control\Director::setUrlParams(): Method removed
 - Coyote.php:71 SilverStripe\ORM\DataObject->manyManyComponent(): DataObject->manyManyComponent() moved to DataObjectSchema. Access through getSchema(). You must manually add static::class as the first argument to manyManyComponent()
Changes not saved; Run with --write to commit to disk

Manually update deprecated API references

Silverstripe CMS 4 introduces many API changes. To update deprecated API references manually, you have to go through each one of your project files. Read the changelogs for 4.0.0 and for subsequent minor releases

Finalising the deprecated API update

At this stage, your site should be using only Silverstripe CMS 4 API logic.

You still have some minor clean up tasks and configuration tweaks to apply, but you're almost done.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 6 - update your entry point

The location of Silverstripe CMS's entry file has changed. Your project and server environment will need to adjust the path to this file from framework/main.php to index.php.

Update your index.php file

You can get a copy of the Silverstripe CMS 4 index.php file at vendor/silverstripe/recipe-core/public/index.php.

If you've created your own index.php or modified version of main.php, you'll need to reconcile those changes with the index.php file you got from recipe-core. Otherwise, just use the generic index.php file recipe-core provides.

Copy your new index.php to your project's web root. Unlike Silverstripe CMS 3, index.php must be present in your web root.

Update your server configuration

If you're using a .htaccess file or web.config file to handle your server configuration, you can get the generic Silverstripe CMS 4 version of those file from vendor/silverstripe/recipe-core/public.

Just like index.php, if you've modified your server configuration file from the one that shipped with Silverstripe CMS 3, you'll need to reconcile your changes into the version retrieve from recipe-core.

Refer to the installation instruction for your platform if your server configuration is not managed via a .htaccess or web.config file.

Finalising the entry point upgrade

At this stage, you could in theory run your project in Silverstripe CMS 4.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 7 - update project structure (optional)

Silverstripe CMS 4 introduces a new recommended project structure (details). Adopting the recommended project structure is optional, but will become mandatory in Silverstripe CMS 5.

Skip to Step 8

Automatically switch to the new structure with the reorganise command

The reorganise command can automatically update your project to use the new recommended structure.

It will search your code and find any occurrence of mysite. It won't replace those occurrences with app however.

upgrade-code reorganise --write

Omit the --write flag if you just want to preview your changes

Manually switch to the new structure

Simply rename your mysite fold to app. Then rename app/code to app/src.

Finalising the reorganise structure

If you've implemented the new PSR-4 auto-loading logic in your composer.json file you'll need to update your namespace mapping.

For example, let's say you had the following autoload attribute in your composer.json.

    // ...
    "autoload": {
        "classmap": [
        "psr-4": {
            "App\\Web\\": "mysite/code/"
    // ...

It will become this:

    // ...
    "autoload": {
        "classmap": [
        "psr-4": {
            "App\\Web\\": "app/src/"
    // ...

You'll need to update the project attribute for your ModuleManifest in your app/src/mysite.yml file. It should now look something like this:

  project: app

At this stage, your project should be functional with the recommended project structure. Note that if you've explicitly referenced any static assets (images, CSS, JS) under mysite, you'll need to rewrite those references.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 8 - switch to public web-root (optional)

Silverstripe CMS 4.1 introduces the concept of public web-root this allows you to move all publicly accessible assets under a public folder (details). This has security benefits as it minimises the possibility that files that are not meant to be access directly get accidentally exposed.

This step is optional and requires Silverstripe CMS 4.1 or greater. It will become mandatory in Silverstripe CMS 5.

Skip to Step 9

Automatically switch to the public web root

The webroot upgrader command will automatically move your files for you.

upgrade-code webroot --write

Omit the --write flag if you want to preview the change.

If you are using a modified index.php, .htaccess, or web.config, you will get a warning.

Manually switch to using the public web root

  • Create a public folder in the root of your project
  • Move the following files and folder to your new public folder

    • index.php
    • .htaccess (if you're using Apache)
    • web.config (if you're using IIS)
    • assets
    • Any favicon files
    • Other common files that should be accessible in your project webroot (e.g. robots.txt, or the .well-known directory)
  • Delete the root resources or _resources directories if present.
  • Run the following command composer vendor-expose to make static assets files accessible via the public directory.

If you are upgrading from Silverstripe CMS 4.0 to Silverstripe CMS 4.1 (or above), you'll need to update index.php before moving it to the public folder. You can get a copy of the generic index.php file from vendor/silverstripe/recipe-core/public. If you've made modifications to your index.php file, you'll need to replicate those into the new public/index.php file.

Finalising the web root migration

You'll need to update your server configuration to point to the public directory rather than the root of your project. Update your .gitignore file so assets and _resources (or resources if using a pre Silverstripe CMS 4.4 release) are still ignored when located under the public folder. Your project should still be functional, although you may now be missing some static assets.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 9 - move away from hardcoded paths for referencing static assets

Silverstripe CMS 4 introduces a new way to reference static assets like images and CSS. This enables innovations like moving the Silverstripe CMS module vendor folder or the public web root.

This change is mandatory if you've completed either step 7 (update project structure) or step 8 (switch to public web-root). If you have skipped these steps, it is strongly recommended, but not mandatory.

Exposing your project static assets

If you have folders under app or mysite that need to be accessible for your project's web root, you need to say so in your composer.json file by adding an entry under extra.expose.

For example, let's say you have scripts, images and css folders under app. You can expose them by adding this content to your composer.json file:

    // ...
    "extra": {
        "branch-alias": {
            "4.x-dev": "4.2.x-dev"
        "expose": [
    // ...

For the change to take affect, run the following command: composer vendor-expose.

Referencing static assets in your PHP code

Wherever you would have use a hardcoded path, you can now use the path/to/file.css syntax. To reference a static file from a module, prefix the path with the module's name (e.g: silverstripe/admin:client/dist/js/bundle.js).

To add some JavaScript and CSS files to your requirements from your PHP code, you could use this syntax:

use SilverStripe\View\Requirements;

# Load your own style and scripts

# Load some assets from a module.
Requirements::script('silverstripe/blog: js/main.bundle.js');

You can SilverStripe\Core\Manifest\ModuleResourceLoader to get the web path of file.

use SilverStripe\Core\Manifest\ModuleResourceLoader;

ModuleResourceLoader::singleton()->resolveURL('silverstripe/blog: js/main.bundle.js');

You can use SilverStripe\View\ThemeResourceLoader to access files from your theme:

use SilverStripe\View\ThemeResourceLoader;

$cssResourcePath = ThemeResourceLoader::inst()->findThemedResource('css/layout.css');
$relativeUrl = ModuleResourceLoader::singleton()->resolveURL($cssResourcePath);

For classes that expect icons, you can specify them with:

namespace App\Admin;

use Page;

class ListingPage extends Page
    private static $icon = 'app/images/sitetree_icon.png';
namespace App\Admin;

use SilverStripe\Admin\ModelAdmin;

class MyCustomModelAdmin extends ModelAdmin
    private static $menu_icon = 'app/images/modeladmin_icon.png';

Referencing static assets in template files

Silverstripe CMS template files accept a similar format for referencing static assets. You will need to go through your assets files and remove hardcoded references.

<img src="$resourceURL(app/images/coyote.png)" />
<% require css("app: css/styles.css") %>

Finalising removal of hardcoded paths for referencing static assets

All your assets should be loading properly now.

This is a good point to commit your changes to your source control system before moving on to the next step.

Step 10 - update database class references

If you've updated your class names to use namespaces you will need to reflect those changes in any existing database fields. For example, if you've renamed your HomePage class to App\HomePage then the database ClassName column needs to be updated to point to the App\HomePage class, otherwise the CMS will tell you that the page is obsolete. This also applies to polymorphic relationships.

There is no automated way to do this, but you can use the list generated in .upgrade.yml and copy it to app/_config/legacy.yml, removing any classes that don't extend DataObject.

    HomePage: App\HomePage

This will automatically update affected columns when you first build the database.

Step 11 - running your upgraded site for the first time

You're almost across the finish line.

Run a dev build

Run a dev/build either on the command line or in your browser.

./vendor/bin/sake dev/build

This should migrate your existing data (non-destructively) to the new Silverstripe CMS 4 structure.

Migrating files

Since the structure of the File DataObject has changed, a new task MigrateFileTask has been added to assist in migration of existing files (see file migration documentation).

./vendor/bin/sake dev/tasks/MigrateFileTask
Rewriting asset references

Your img and a tag references to your assets may now be pointing to a location in your assets folder that has been moved. There is a task available which will look through all your tables containing HTMLText and HTMLVarchar fields looking for broken references and then rewrite them to the new location of the file.

./vendor/bin/sake dev/tasks/TagsToShortcodeTask

Any other script that needs running

Some third party modules may include their own migration tasks. Take a minute to consult the release notes of your third party dependencies to make sure you haven't missed anything.