Silverstripe version upgrade
Skill jsirish/silverstripe-skills/skills/silverstripe-version-upgrade
Agent skills for Silverstripe CMS development — version upgrades, Elemental, the Essentials stack, and deployment workflows
npx -y skills add jsirish/silverstripe-skills --skill silverstripe-version-upgradeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Complete workflow for upgrading Silverstripe CMS projects between major versions on the SS4+ line (e.g. SS4 → SS5, SS5 → SS6). Covers assessment, dependency updates, config migration, data migration tasks, testing, and deployment. For legacy SS3 → SS4 upgrades use the silverstripe-3-to-4-upgrade skill instead — that migration is structurally different.
SKILL.md
28.7 KB, as published. Nobody here has run it
Silverstripe Version Upgrade Skill
Repeatable workflow for upgrading Silverstripe CMS projects (e.g. SS4 → SS5) in Dynamic Agency's module ecosystem and DDEV-based local development.
Scope: This skill covers SS4 and later major-version bumps (SS4 → SS5, SS5 → SS6) — a recurring, recipe- and dependency-driven upgrade. For a legacy SS3 → SS4 upgrade, use the silverstripe-3-to-4-upgrade skill instead: that is a one-time structural migration (PSR-4 namespacing,
mysite→app,public/directory, DB schema preflight, Blocks→Elemental) with little mechanical overlap with this workflow.
Upgrade Phases (Summary)
| Phase | Key Actions |
|---|---|
| 1. Assessment | Audit versions, packages, incompatibilities; clone legacy instance for VR baseline |
| 2. Branch & PHP | Create feature branch, update PHP to ^8.1 |
| 3. Dependencies | Update recipes with --no-update, resolve, vendor-expose |
| 4. Config Migration | Fix extension relocations, ORM relationships, templates |
| 5. Data Migration | Write idempotent BuildTasks for link/data migrations |
| 6. Build & Verify | dev/build, run tasks, check frontend + CMS |
| 7. Code Quality | PHPCS, PHPStan, annotation fixes |
| 8. Commit & PR | Conventional commit, checklist PR body |
| 9. UAT | Deploy, run tasks, verify, troubleshoot |
| 10. Production | Merge, deploy, run tasks, verify |
Do not rationalize
Every phase gate below requires evidence: the actual command output, not a recollection or an inference. "Seems right" is insufficient. The shortcuts agents talk themselves into, and the required counter-behavior:
| Rationalization | Required behavior |
|---|---|
| "The build passed, so the migration ran" | Run each migration task and paste its output, including row counts. dev/build succeeding proves nothing about task execution. |
| "The vendor diff looks fine" | Run ddev composer validate and ddev composer show --direct, paste the output. Eyeballing composer.lock is not validation. |
| "Tests probably still pass" | Run the suite (ddev exec vendor/bin/phpunit) and paste the summary line. No run, no claim. |
| "The homepage loads, so the site works" | Curl one URL per page type and paste the HTTP codes (see Phase 6 and 10.4). One page proves one template. |
| "dev/build ran without errors, so the schema is correct" | Paste the build output showing the table and field changes you expected. A silent build can mean silently ignored config (see classname_value_remapping). |
| "That warning is probably pre-existing" | Grep the pre-upgrade branch for the same warning and paste both results. Confirmed pre-existing or it is yours. |
Phase 1: Assessment & Discovery
1.1 Pre-Flight
ddev start
git branch -a | grep -i 'ss5\|silverstripe-5\|upgrade'
ddev composer show silverstripe/framework | grep versions
ddev php -v
# SS5 requires PHP 8.1+; SS6 requires PHP 8.3+ — update .ddev/config.yaml if needed
1.2 Audit Packages
ddev composer show | grep 'dynamic/\|silverstripe/\|dnadesign/'
1.3 Incompatible Packages (Common)
| Package | Action |
|---|---|
lekoala/silverstripe-debugbar | Remove |
ryanpotter/silverstripe-cms-theme | Remove (SS5 built-in) |
fractas/elemental-stylings | Remove (use native styles) |
sheadawson/silverstripe-linkable | Replace with silverstripe/linkfield |
dynamic/silverstripe-company-config | Remove (merged into base-site) |
dynamic/silverstripe-template-config | Remove (merged into base-site) |
SS6-specific
| Package | Action |
|---|---|
nathancox/embedfield | Replace with fromholdio/silverstripe-embedfield ^5.1 |
[!TIP] If the project uses
sheadawson/silverstripe-blocks, migrate to Elemental before SS5 upgrade usingdynamic/silverstripe-blocks-to-elemental-migrator.
1.4 Legacy instance (for VR baseline)
Clone the current SS4 site as a side-by-side reference before starting the upgrade. This gives you a pixel-perfect local baseline to diff against during VR verification — same approach as the SS3→SS4 upgrade pattern.
Naming convention: clone into ~/Sites/{project}-legacy so the upgrade lives in ~/Sites/{project}.
cd ~/Sites
git clone <repo> {project}-legacy
cd {project}-legacy
git checkout main # or the current production branch
ddev config --project-name {project}-legacy
ddev start
ddev auth ssh
ddev exec ./sync.sh # sync prod DB and assets
This gives you https://{project}-legacy.ddev.site — a running SS4 instance against the same data the upgrade will use. In the Build & Verify phase you'll diff this against the SS5 upgrade to confirm visual parity.
Phase 2–3: Branch & Dependencies
git checkout -b feature/silverstripe-5-upgrade
# Update PHP requirement in composer.json
# Change "php": "^7.4" to "php": "^8.1" (SS5 requires PHP 8.1+)
# Update core
ddev composer require silverstripe/recipe-cms:^5 dynamic/recipe-silverstripe-base-site:^5 --no-update
# Update elemental packages (versions vary per package — verify on Packagist)
# Add each package individually, e.g.:
ddev composer require dynamic/silverstripe-elemental-accordion:^5.0 --no-update
# Remove incompatible
ddev composer remove lekoala/silverstripe-debugbar --no-update
# Resolve
ddev composer update --with-all-dependencies
ddev composer vendor-expose
SS6 Variant
For SS6 upgrades, adapt the branch and dependency steps:
git checkout -b feature/silverstripe-6-upgrade
# Verify PHP version — SS6 requires PHP 8.3+
ddev php -v | grep -oP 'PHP \K[0-9]+\.[0-9]+'
# Must be 8.3 or higher — update .ddev/config.yaml if needed
# Change "php": "^8.1" to "php": "^8.3" in composer.json
# Update core recipe
ddev composer require silverstripe/recipe-cms:^3 dynamic/recipe-silverstripe-base-site:^8 --no-update
# Update elemental packages (branch 6 for most elemental modules)
ddev composer require dynamic/silverstripe-elemental-accordion:^6.0 --no-update
# Add SS6-required packages that SS5 had as transitive
ddev composer require silverstripe/htmleditor-tinymce:^1.0 --no-update
# Replace deprecated packages (linkfield 4.x is CMS 5 only; the SS6 line is 5.x.
# Run the linkable data migration on SS5 with linkfield ^4 BEFORE this bump)
ddev composer require --no-update silverstripe/linkfield:^5.0
ddev composer remove --no-update sheadawson/silverstripe-linkable
# Remove incompatible
ddev composer remove --no-update nathancox/embedfield
# Resolve
ddev composer update --with-all-dependencies
ddev composer vendor-expose
Recipe-first: When consuming the Dynamic Essentials ecosystem, require only the root recipe:
ddev composer require dynamic/recipe-silverstripe-essentials-website:^3@devThe recipe's@devconstraints cascade the entire tree automatically. Individual module constraints are only needed if forcing source install viapreferred-install: dynamic/*: source.
silverstripe/vendor-pluginbump: SS6 Dynamic modules require^3. If the project root pins^2.0(SS5 default), Composer will conflict. Apply before runningcomposer update:ddev composer require silverstripe/vendor-plugin:^3 --no-update
[!WARNING] SS6 blocker —
silverstripeltd/betamask:silverstripeltd/betamask ^0.0.1requiressilverstripe/admin ^2.1(SS5-only). Check for an SS6-compatible release before removing it:ddev composer show -a silverstripeltd/betamask # check for an SS6-compatible release ddev composer remove silverstripeltd/betamask # if none found
Evidence gate (Phase 3): before moving to config migration, paste the output of:
ddev composer validate
ddev composer show --direct # confirm expected major versions resolved
A clean composer update exit code is not the gate; the resolved version list is.
Phase 4: Configuration Migration
Key areas:
- Extension relocations:
HeaderImageExtensionmoved from base-site → site-tools - ORM strictness: SS5 requires explicit
has_oneback-references forhas_many - Template updates: Sort field
SortOrder→Sort, link$Link→$URL - Annotations: Use
@property ?stringfor nullable fields in PHP 8.1+
SS6 note: BuildTask signature changed (
run($request)→execute(InputInterface, PolyOutput): int). See references/data-migration-tasks.md for the SS6 Symfony Console Command template.
SS6 Configuration Migration
Namespace and class renames
SS6 relocated several core framework classes. Every use statement referencing an old FQCN causes a PHP fatal error at runtime (often first surfacing on dev/build). Grep for each old name and replace:
| Old (SS5) | New (SS6) |
|---|---|
SilverStripe\View\ViewableData | SilverStripe\Model\ModelData |
SilverStripe\View\ArrayData | SilverStripe\Model\ArrayData |
SilverStripe\ORM\ArrayList | SilverStripe\Model\List\ArrayList |
SilverStripe\ORM\ValidationResult | SilverStripe\Core\Validation\ValidationResult |
SilverStripe\ORM\ValidationException | SilverStripe\Core\Validation\ValidationException |
# Run each grep across app code, module code, and theme code
rg "SilverStripe\\\\View\\\\ViewableData" app/src/ src/ themes/*/code/
rg "SilverStripe\\\\View\\\\ArrayData" app/src/ src/ themes/*/code/
rg "SilverStripe\\\\ORM\\\\ArrayList" app/src/ src/ themes/*/code/
rg "SilverStripe\\\\ORM\\\\ValidationResult" app/src/ src/ themes/*/code/
rg "SilverStripe\\\\ORM\\\\ValidationException" app/src/ src/ themes/*/code/
The wernerkrauss/silverstripe-rector SS6 level set automates most of these renames (see references/code-quality.md), but always run the greps afterward: string references in config YAML, Injector definitions, and docblocks are not rewritten by Rector.
Typed signature requirements
Two related changes cause PHP declaration-compatibility fatals if missed:
validate()overrides must declare the return type. AnyDataObjectsubclass overridingvalidate()must usepublic function validate(): ValidationResult(with the newSilverStripe\Core\Validation\ValidationResultimport). PHP throws a fatal declaration-compatibility error otherwise.ModelDatamagic-method overrides must match parent signatures. Subclasses ofModelData(formerlyViewableData) that override__get,__set,__isset,hasField,getField,setField, etc. must add the typed parameters and return types the SS6 parent declares.
# Find validate() overrides missing the return type
rg "public function validate\(\)(?!\s*:)" app/src/ src/ --pcre2
# Find ModelData subclasses, then inspect their overridden magic methods
rg "extends ModelData|extends ViewableData" app/src/ src/
BuildTask signature change
SS6 changes run($request) → execute(InputInterface, PolyOutput): int returning Command::SUCCESS.
Key differences:
| SS4/SS5 | SS6 |
|---|---|
protected $title = '...' | protected string $title = '...' |
protected $description = '...' | protected static string $description = '...' |
private static $segment = '...' | protected static string $commandName = '...' (required) |
public function run($request) | protected function execute(InputInterface $input, PolyOutput $output): int |
echo / $this->log() | $output->writeln() |
| Implicit return | return Command::SUCCESS |
forTemplate() return type enforcement
SS6 enforces : string return type on forTemplate(). Never return false — return `` (empty string) instead.
// SS5 (still works but triggers deprecation)
public function forTemplate() { return false; }
// SS6
public function forTemplate(): string { return ''; }
BaseElement::getDescription() removed
SS6 Elemental removed BaseElement::getDescription(). Use private static string $class_description instead:
// SS5
public function getDescription() { return "My element"; }
// SS6
private static string $class_description = "My element";
DDEV database socket config
SS6 defaults to MySQL unix socket connections. DDEV uses TCP, so add to .ddev/config.yaml:
web_environment:
- SS_DATABASE_SERVER=db
Without this, dev/build fails with "Connection refused."
TinyMCE extraction
SS6 extracted TinyMCE from silverstripe/admin into silverstripe/htmleditor-tinymce ^1.0. If missing, the CMS Content field silently degrades to a plain textarea (data-editor="textarea" instead of data-editor="tinyMCE").
[!NOTE] Essentials recipe exception: in Essentials SS6 recipe projects the package arrives transitively through the recipe, and an explicit require causes conflicts — check
composer why silverstripe/htmleditor-tinymcebefore adding it (see thesilverstripe-essentials-websiteskill).
Fix: Add to composer.json under require (not require-dev):
"silverstripe/htmleditor-tinymce": "^1.0"
Diagnostic:
document.querySelector('[data-editor]').dataset.editor
// Returns "textarea" instead of "tinyMCE" → missing package
Theme template API changes: Linkable → LinkField
When migrating from sheadawson/silverstripe-linkable to silverstripe/linkfield:
| Linkable (SS5) | Linkfield (SS6) | Notes |
|---|---|---|
$Link | $URL | getURL() not getLink() |
$LinkURL | $URL | Consistent — use $URL |
$OpenInNewWindow | $OpenInNew | Renamed attribute |
$MenuTitle | $Title | Different semantics — getMenuTitle() returns type label |
$Site (SocialLink) | $SocialChannel | Enum → varchar mapping |
$X.setStyle('classes') | Explicit <a class="..."> | Linkfield renders bare links |
<% loop $HasOneLink %> | <% with $HasOneLink %> | has_one is not iterable in SS6 |
$ElementLink.LinkURL | $ElementLink.URL | Namespace change on sub-properties |
SS6 silent config breakers
SeoExtension removal — Remove from SiteTree.extensions if using a third-party search provider:
SilverStripe\CMS\Model\SiteTree:
extensions:
- Dynamic\Base\Extension\SeoExtension # REMOVE — causes PHP worker hangs in SS6
PasswordValidator — SS6 defaults to EntropyPasswordValidator. Old RulesPasswordValidator config is silently ignored:
# SS6 format (NOT the old min_test_score / test_names pattern)
SilverStripe\Security\PasswordValidator:
password_strength: 3 # 0-4 scale
Session cookie defaults — SS6 sets SameSite=Strict, cookie_secure=true. All existing sessions invalidated on first deploy — expected and normal. Verify:
curl -sI https://site.example.com/ | grep -i 'set-cookie'
# Expected: PHPSESSID=...; path=/; secure; HttpOnly; SameSite=Strict
classname_value_remapping YAML key renamed — In SS4/5 the key was SilverStripe\ORM\DatabaseAdmin. In SS6 the class no longer exists; use SilverStripe\Dev\Command\DbBuild instead. Using the old key is silently ignored — dev/build runs without error but no remapping occurs, leaving legacy class names in the DB and causing "obsolete type" warnings in the CMS for every affected page.
# SS4/SS5 (WRONG in SS6 — silently ignored)
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
OldClass: NewClass
# SS6 (correct)
SilverStripe\Dev\Command\DbBuild:
classname_value_remapping:
OldClass: NewClass
After correcting the key, a single dev/build flush=1 migrates all affected rows in both SiteTree and SiteTree_Live. The build output will confirm: "Correcting obsolete ClassName values for N outdated types." Side effect: migrated pages gain a draft/live diff and show as MODIFIED — republish them afterward.
Phase 5: Data Migration
See references/data-migration-tasks.md for the BuildTask pattern, migration principles, and common scenarios (Linkable → Link, ManyMany → LinkField).
Evidence gate (Phase 5): a migration step is done when you can paste, for each task:
- The task's own output (migrated / skipped / broken counts).
- A row-count query against the target tables, compared to the source count:
SELECT COUNT(*) FROM <SourceTable>; -- before
SELECT COUNT(*) FROM <TargetTable>; -- after
SELECT COUNT(*) FROM <TargetTable>_Live; -- if versioned
SELECT COUNT(*) FROM <TargetTable>_Versions;
Counts must match, or every row of the delta must be accounted for (skipped classes, intentional exclusions). "The task finished without errors" is not the gate.
Phase 6: Build & Verify
ddev sake dev/build "flush=1"
ddev sake dev/tasks/<task-segment>
Verify: Homepage, carousel, navigation, footer, CMS admin, elemental blocks.
Evidence gate (Phase 6): paste the dev/build output (expected table/field changes present, no obsolete-type warnings you cannot explain) and the HTTP status of one URL per page type:
for u in / /<page-type-urls>; do
curl -s -o /dev/null -w "%{http_code} $u\n" "https://{project}.ddev.site$u"
done
Visual regression (optional but recommended)
Diff the SS5 upgrade against the legacy SS4 instance from Phase 1.4:
# Path to the installed skill; adjust for your agent's skills dir
VR=~/.claude/skills/visual-regression-upgrade
# Crawl the legacy site for a URL list
cd ~/Sites/{project}-legacy
python "$VR"/scripts/crawl_urls.py \
--url https://{project}-legacy.ddev.site --limit 30 --out paths.txt
# Capture + diff both environments
cd ~/Sites/{project}
python "$VR"/scripts/capture.py \
--prod https://{project}-legacy.ddev.site \
--local https://{project}.ddev.site \
--paths-file ../{project}-legacy/paths.txt \
--out ./vr-out
python "$VR"/scripts/diff_report.py \
--in ./vr-out --out ./vr-out/report
See the visual-regression-upgrade skill (from jsirish/workflow-skills, installed separately) for setup, auth, mask config, and report interpretation.
Key Discoveries & Gotchas
[!WARNING] Raw SQL Data Migrations: When migrating from deprecated or obsoleted modules (such as legacy
dynamic/silverstripe-blocks), the modern ORM will not map legacy database columns (like$dbproperties) because the field definitions have been removed from the class. You must rely on rawDB::query()calls to preserve and remap data (e.g., copying oldPageLinkIDfields directly into newLinkFieldinstances).
[!NOTE] Empty Layout Containers: During block to element mapping, migrating structural containers (like Accordions or Promo galleries) that have exactly 0 items within them is intentional. Preserving the empty wrapper maintains its hierarchical placement in the DOM so editors can populate it post-upgrade without losing context.
[!TIP] AJAX Lazy Loading Optimization: Pre-generating Image asset manipulations (e.g.,
ScaleWidthorFill) and employing AJAX lazy-loading on heavily populated pages (such as dense galleries) is required. Rendering 200+ raw Image objects in a single PHP payload causes hard pre-prod timeouts in SS5. Note: in SS5,PageController::init()will still fire on AJAX endpoints unless explicitly bypassed.
[!IMPORTANT] Data duplication / Subtitle: The unified templates in SS5 ElementContent might render both
TitleandSubTitledomains redundantly. SS4 layouts that abused DB columns to handle split headings will show duplicate lines post-migration unless the legacy entries are scrubbed.
[!NOTE] SS5
httpError(): ThehttpError()routine in SS5 throws anHTTPResponse_Exception. Following the call with a simplereturnaids static analysis flow and squashes PHP linting notices without impacting application state.
[!WARNING] Elemental Styles &
fractas/elemental-stylings: In SS4, thefractas/elemental-stylingsmodule prefixed the elemental style values withstyle-(e.g.,style-modal,style-blue). In SS5, native styles are used, andgetStyleVariant()returns the raw value (e.g.,modal). If templates or SCSS rely on thestyle-prefix, you must either update the templates/SCSS, update the values in the database, OR write a simpleupdateStyleVariantDataExtension to re-apply the prefix.
SS6 Breaking Changes
[!WARNING] Core classes renamed in SS6.
ViewableData→ModelData,ArrayDatamoved toSilverStripe\Model,ArrayList→SilverStripe\Model\List\ArrayList,ValidationResult→SilverStripe\Core\Validation\ValidationResult. Everyusestatement referencing an old FQCN is a fatal error at runtime. Systematic grep, one per rename: see Phase 4.Typed signatures enforced in SS6.
validate()overrides need: ValidationResult;ModelDatasubclasses overriding__get,hasField,__isset, etc. need typed parameters and return types matching the parent. Both are PHP declaration-compatibility fatals if missed. See Phase 4.BuildTask signature changed in SS6.
run($request)→execute(InputInterface $input, PolyOutput $output): intreturningCommand::SUCCESS. All custom BuildTask subclasses must be updated.TinyMCE extracted.
silverstripe/htmleditor-tinymce ^1.0must be inrequire(notrequire-dev). If missing, CMS Content fields silently degrade to plain textareas — no error, no console warning.
classname_value_remappingYAML key changed. SS6 renamedSilverStripe\ORM\DatabaseAdmin→SilverStripe\Dev\Command\DbBuild. The old key is silently ignored — remapping never runs, pages keep obsolete class names. Fix the YAML key and re-rundev/build flush=1.DB connection default changed. SS6 defaults to unix socket. DDEV requires
SS_DATABASE_SERVER=dbin.ddev/config.yaml.Linkable removed.
sheadawson/silverstripe-linkablehas no SS6 version. Replace withsilverstripe/linkfield ^5(the 4.x line is CMS 5 only; run the linkable data migration on SS5 with linkfield^4BEFORE the SS6 bump). Template API changes documented above.Embedfield replaced.
nathancox/embedfieldhas no SS6 version. Replace withfromholdio/silverstripe-embedfield ^5.1.
CMSPageAddControllerremoved. Any module extension bound to it (commonly anupdatePageOptions(FieldList $fields)hook on the "Add new page" flow) is dead code with no error - the extended class no longer exists, so the hook never fires.
- Add fields to the add form: extend
SilverStripe\CMS\Forms\CMSMainAddFormwithupdateFields(FieldList $fields). The page-type field is now namedRecordType(wasPageType), anOptionsetField- insert after it.- Act on create: extend
SilverStripe\CMS\Controllers\CMSMainwithupdateDoAdd(DataObject $record, Form $form)(CMSMainAddForm::doAdd()fires$controller->extend('updateDoAdd', $record, $form)).- Read the submitted values:
$form->getData()- by the timeupdateDoAddfires,FormRequestHandler::httpSubmission()has already populated the form from the POST vars vialoadDataFrom(), sogetData()reflects the submission, not stale construction-time data (verified againstsilverstripe/frameworkandsilverstripe/cmsSS6 source:CMSMainAddForm extends Form, no override of the base submission flow).Form::getRequestData()does not exist in any SilverStripe version - do not use it.- Verify in the live CMS, not just a unit test that calls the hook method directly - the wiring (which class the extension binds to) is exactly what silently breaks, and a direct method-call test passes regardless of the binding.
Real-world hit:
dynamic/silverstripe-elemental-templates(its template-picker "Step 3" dropdown), fixed indynamic/silverstripe-elemental-templates#83/#84.
Phase 7: Code Quality
See references/code-quality.md for PHPCS/PHPStan steps.
PHPStan vendor path: Only scan
app/srcandapp/tests— never includevendor/inphpstan.neonpaths. Vendor errors such asmethod.childReturnTypecannot be baselined (PHPStan re-reports them regardless). File upstream bugs in the relevant module repo. Full details in references/code-quality.md.
Evidence gate (Phase 7): paste the summary line of each tool run (PHPUnit test/assertion counts, PHPCS error count, PHPStan error count). "Probably still passes" does not clear this gate; only a run in this working tree does.
Phase 8: Commit & PR
Conventional commit format, checklist PR body.
Phase 9: UAT
Deploy to UAT, run migration tasks, verify frontend and CMS, troubleshoot.
Phase 10: Production
10.1 Deployment sequencing decision
If your SS6 upgrade also bundles AI modules, pre-1.0 packages, or other high-risk additions, consider splitting into two deploys — SS6 cutover first, followed by the new modules. Stacking pre-release packages on a cutover deploy increases blast radius and makes rollback more complex.
10.2 DeployHQ (or comparable CD) repoint
The deploy branch must be changed from the old SS5 branch (e.g., 4 or 5) to master (SS6). Do this right before deploying, not in advance — the old branch is the rollback target.
Pre-deploy checks:
# Verify composer install succeeds
composer install --no-dev
# Verify all runtime deps are in "require", not "require-dev"
# Notably: silverstripe/htmleditor-tinymce must be in "require"
composer show --direct | grep htmleditor
Private repo access: If the upgrade adds modules from private GitHub repos (e.g., silverstripeltd/ai-*), ensure the production server has an SSH deploy key with read access to the organization before cutover day. Test with a manual composer install if possible.
10.3 Database strategy
Option A (recommended): Push local/UAT DB to prod before cutover. Run migrations locally, then use deploy.sh --db to push the already-migrated database to production. No migration tasks needed on prod — it's a drop-in replacement.
Option B: Run migration on production's existing DB. Deploy SS6 code, run dev/build flush=1, then run each migration task sequentially. Riskier — if a migration fails mid-way, the site is partially broken and requires schema repair.
10.4 Post-deploy smoke test
# Key pages load
for u in / /work /blog /admin/ /about/about-us; do
curl -sI "https://$DOMAIN$u" | head -3
done
# Verify no redirect loop
curl -sI "https://$DOMAIN/" | grep -c "301\|302" # should be 0
# Verify CMS renders (WYSIWYG check — log in, edit a page)
# Expected: data-editor="tinyMCE" on Content field
10.5 Expected post-deploy behaviors
- All users logged out on first deploy — SS6's
SameSite=Strictsession cookie default invalidates existing sessions. Normal and expected. - TinyMCE missing → plain textarea — add
silverstripe/htmleditor-tinymce ^1.0torequire. - Migration tasks not found
dev/build flush=1not yet run, orcommandNameproperty missing from the Symfony Console task. - Custom
forTemplate()methods crash — add: stringreturn type.
Reference Documentation
| Topic | File |
|---|---|
| Data Migration Tasks | data-migration-tasks.md |
| SS5 Version Map | version-map.md |
| SS6 Version Map | version-map-ss6.md |
| Code Quality | code-quality.md |
Recipe Branch Convention (SS6)
When branching repositories for an SS6 upgrade, use:
| CMS Version | Recipe Branch | Elemental Branch | Base-site Branch |
|---|---|---|---|
| SS4 | 1 | master (deprecated) | 5 |
| SS5 | 2 | 5 | 7 |
| SS6 | 3 | 6 | 8 |
Recipes follow recipe-cms major version numbering. Elemental modules follow their own major version (elemental ^6 = branch 6). Set the new version branch as the default branch on GitHub for each recipe/module repo.
Related skills
- silverstripe-3-to-4-upgrade — the prior, structurally different leg. Use it for legacy SS3 → SS4 projects before this skill applies.
- ss5-data-migration / ss6-data-migration — version-specific data-migration runbooks for the BuildTasks in Phase 5.
visual-regression-upgrade(from jsirish/workflow-skills, installed separately) — capture pixel diffs against the legacy instance to confirm parity (also referenced inline in the Build & Verify phase).