agentsclimarketplace

Silverstripe version upgrade

Skill jsirish/silverstripe-skills/skills/silverstripe-version-upgrade

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.From its SKILL.md

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.

2 things 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.
  • runs commandsInstructs the agent to run 8 commands, including `ddev start` and 7 more.

SKILL.md

28.7 KB, ~7.2k tokens by cl100k_base, 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).

What ships with it: 5 files

29.8 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. 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.