agentsclimarketplace

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

Install
npx -y skills add jsirish/silverstripe-skills --skill silverstripe-version-upgrade

Assembled 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, mysiteapp, public/ directory, DB schema preflight, Blocks→Elemental) with little mechanical overlap with this workflow.

Upgrade Phases (Summary)

PhaseKey Actions
1. AssessmentAudit versions, packages, incompatibilities; clone legacy instance for VR baseline
2. Branch & PHPCreate feature branch, update PHP to ^8.1
3. DependenciesUpdate recipes with --no-update, resolve, vendor-expose
4. Config MigrationFix extension relocations, ORM relationships, templates
5. Data MigrationWrite idempotent BuildTasks for link/data migrations
6. Build & Verifydev/build, run tasks, check frontend + CMS
7. Code QualityPHPCS, PHPStan, annotation fixes
8. Commit & PRConventional commit, checklist PR body
9. UATDeploy, run tasks, verify, troubleshoot
10. ProductionMerge, 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:

RationalizationRequired 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)

PackageAction
lekoala/silverstripe-debugbarRemove
ryanpotter/silverstripe-cms-themeRemove (SS5 built-in)
fractas/elemental-stylingsRemove (use native styles)
sheadawson/silverstripe-linkableReplace with silverstripe/linkfield
dynamic/silverstripe-company-configRemove (merged into base-site)
dynamic/silverstripe-template-configRemove (merged into base-site)

SS6-specific

PackageAction
nathancox/embedfieldReplace with fromholdio/silverstripe-embedfield ^5.1

[!TIP] If the project uses sheadawson/silverstripe-blocks, migrate to Elemental before SS5 upgrade using dynamic/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@dev The recipe's @dev constraints cascade the entire tree automatically. Individual module constraints are only needed if forcing source install via preferred-install: dynamic/*: source.

silverstripe/vendor-plugin bump: SS6 Dynamic modules require ^3. If the project root pins ^2.0 (SS5 default), Composer will conflict. Apply before running composer update:

ddev composer require silverstripe/vendor-plugin:^3 --no-update

[!WARNING] SS6 blocker — silverstripeltd/betamask: silverstripeltd/betamask ^0.0.1 requires silverstripe/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: HeaderImageExtension moved from base-site → site-tools
  • ORM strictness: SS5 requires explicit has_one back-references for has_many
  • Template updates: Sort field SortOrderSort, link $Link$URL
  • Annotations: Use @property ?string for 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\ViewableDataSilverStripe\Model\ModelData
SilverStripe\View\ArrayDataSilverStripe\Model\ArrayData
SilverStripe\ORM\ArrayListSilverStripe\Model\List\ArrayList
SilverStripe\ORM\ValidationResultSilverStripe\Core\Validation\ValidationResult
SilverStripe\ORM\ValidationExceptionSilverStripe\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. Any DataObject subclass overriding validate() must use public function validate(): ValidationResult (with the new SilverStripe\Core\Validation\ValidationResult import). PHP throws a fatal declaration-compatibility error otherwise.
  • ModelData magic-method overrides must match parent signatures. Subclasses of ModelData (formerly ViewableData) 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/SS5SS6
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 returnreturn 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-tinymce before adding it (see the silverstripe-essentials-website skill).

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$URLgetURL() not getLink()
$LinkURL$URLConsistent — use $URL
$OpenInNewWindow$OpenInNewRenamed attribute
$MenuTitle$TitleDifferent semantics — getMenuTitle() returns type label
$Site (SocialLink)$SocialChannelEnum → 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.URLNamespace 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 ignoreddev/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:

  1. The task's own output (migrated / skipped / broken counts).
  2. 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 $db properties) because the field definitions have been removed from the class. You must rely on raw DB::query() calls to preserve and remap data (e.g., copying old PageLinkID fields directly into new LinkField instances).

[!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., ScaleWidth or Fill) 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 Title and SubTitle domains 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(): The httpError() routine in SS5 throws an HTTPResponse_Exception. Following the call with a simple return aids static analysis flow and squashes PHP linting notices without impacting application state.

[!WARNING] Elemental Styles & fractas/elemental-stylings: In SS4, the fractas/elemental-stylings module prefixed the elemental style values with style- (e.g., style-modal, style-blue). In SS5, native styles are used, and getStyleVariant() returns the raw value (e.g., modal). If templates or SCSS rely on the style- prefix, you must either update the templates/SCSS, update the values in the database, OR write a simple updateStyleVariant DataExtension to re-apply the prefix.

SS6 Breaking Changes

[!WARNING] Core classes renamed in SS6. ViewableDataModelData, ArrayData moved to SilverStripe\Model, ArrayListSilverStripe\Model\List\ArrayList, ValidationResultSilverStripe\Core\Validation\ValidationResult. Every use statement 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; ModelData subclasses 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): int returning Command::SUCCESS. All custom BuildTask subclasses must be updated.

TinyMCE extracted. silverstripe/htmleditor-tinymce ^1.0 must be in require (not require-dev). If missing, CMS Content fields silently degrade to plain textareas — no error, no console warning.

classname_value_remapping YAML key changed. SS6 renamed SilverStripe\ORM\DatabaseAdminSilverStripe\Dev\Command\DbBuild. The old key is silently ignored — remapping never runs, pages keep obsolete class names. Fix the YAML key and re-run dev/build flush=1.

DB connection default changed. SS6 defaults to unix socket. DDEV requires SS_DATABASE_SERVER=db in .ddev/config.yaml.

Linkable removed. sheadawson/silverstripe-linkable has no SS6 version. Replace with silverstripe/linkfield ^5 (the 4.x line is CMS 5 only; run the linkable data migration on SS5 with linkfield ^4 BEFORE the SS6 bump). Template API changes documented above.

Embedfield replaced. nathancox/embedfield has no SS6 version. Replace with fromholdio/silverstripe-embedfield ^5.1.

CMSPageAddController removed. Any module extension bound to it (commonly an updatePageOptions(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\CMSMainAddForm with updateFields(FieldList $fields). The page-type field is now named RecordType (was PageType), an OptionsetField - insert after it.
  • Act on create: extend SilverStripe\CMS\Controllers\CMSMain with updateDoAdd(DataObject $record, Form $form) (CMSMainAddForm::doAdd() fires $controller->extend('updateDoAdd', $record, $form)).
  • Read the submitted values: $form->getData() - by the time updateDoAdd fires, FormRequestHandler::httpSubmission() has already populated the form from the POST vars via loadDataFrom(), so getData() reflects the submission, not stale construction-time data (verified against silverstripe/framework and silverstripe/cms SS6 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 in dynamic/silverstripe-elemental-templates#83/#84.

Phase 7: Code Quality

See references/code-quality.md for PHPCS/PHPStan steps.

PHPStan vendor path: Only scan app/src and app/tests — never include vendor/ in phpstan.neon paths. Vendor errors such as method.childReturnType cannot 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=Strict session cookie default invalidates existing sessions. Normal and expected.
  • TinyMCE missing → plain textarea — add silverstripe/htmleditor-tinymce ^1.0 to require.
  • Migration tasks not found dev/build flush=1 not yet run, or commandName property missing from the Symfony Console task.
  • Custom forTemplate() methods crash — add : string return type.

Reference Documentation

TopicFile
Data Migration Tasksdata-migration-tasks.md
SS5 Version Mapversion-map.md
SS6 Version Mapversion-map-ss6.md
Code Qualitycode-quality.md

Recipe Branch Convention (SS6)

When branching repositories for an SS6 upgrade, use:

CMS VersionRecipe BranchElemental BranchBase-site Branch
SS41master (deprecated)5
SS5257
SS6368

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).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.