Files
t3bootstrap-upgrade-skills/typo3-v11-to-v14-ddev-upgrade/SKILL.md
T
melnicenko 4e4af79e4f Initial commit: TYPO3 v11→v14 upgrade / design-parity / konsoleH-deploy skills
Three reusable, placeholder-based Claude Code skills distilled from the IVV
Aachen TYPO3 v11→v14 project:
- typo3-v11-to-v14-ddev-upgrade
- typo3-t3bootstrap-live-design-parity (v1.1)
- typo3-konsoleh-staging-deploy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:01:06 +02:00

9.2 KiB
Raw Blame History

name, description, metadata
name description metadata
typo3-v11-to-v14-ddev-upgrade Bring an existing TYPO3 v11 site up and booting on TYPO3 v14 LTS (v14.3) locally under DDEV, starting from a v11 SQL dump + the old sitepackage. Use when upgrading/migrating a TYPO3 v11 (or v12/13) project to v14 for local dev, when a v11 database dump must be made to boot on v14, or when a t3bootstrap-based v11 site needs its composer/config/schema/upgrade-wizards/site-sets brought to v14. Covers: composer.json rework to ^14 + private git repos & composer registry auth, config/system/settings.php, the v14 .htaccess, database:updateschema, backend admin, upgrade wizards (incl. confirmable + one-way ones), language packs, wiring t3bootstrap TypoScript via site sets, and the recurring v11->v14 traps. NOT for fresh installs and NOT for server deployment (see the konsoleH staging-deploy skill for that).
author version
Wappler 1.0

TYPO3 v11 → v14 upgrade (local, DDEV)

Get an existing TYPO3 v11 site installable and booting on v14 LTS (14.3.x) under DDEV, from a v11 SQL dump + the old sitepackage. This is the "make it run" phase — a later phase matches the frontend design (see the t3bootstrap live-design parity skill) and another deploys to staging (see the konsoleH staging-deploy skill).

Work one step at a time and verify each before moving on. ddev snapshot before anything destructive (especially upgrade wizards — several are one-way). Never let a live secret appear in a command. Many errors here look like config bugs but are data/schema/host issues — always confirm the actual error before "fixing".

Placeholders to resolve first

  • <PROJECT_DIR> — the DDEV project root (docroot public/).
  • <SITE_ID> — the site identifier folder under config/sites/<SITE_ID>/.
  • <SITE_BASE_HOST> — the site's base host from config/sites/<SITE_ID>/config.yaml (e.g. the *.ddev.site host). The FE only answers on this host, NOT localhost.
  • <REGISTRY_URL> — the private Composer registry for the theme/vendor packages, if any (e.g. a Gitea .../api/packages/<vendor>/composer URL). <GIT_REPOS> — any gitea@/GitHub SSH git repos in composer.json repositories.
  • <TEMPLATE_PACKAGE> — the sitepackage's composer name (⚠ often ≠ repo name — read the repo's own composer.json). <TEMPLATE_EXTKEY> — its extension key (keep it stable across the rename).

Step 0 — DDEV up + import the dump (read-only baseline)

ddev start; import the v11 dump into the db database (ddev import-db --file=dump-*.sql or ddev mysql < dump.sql). Confirm PHP/DB versions match the target (v14.3 wants PHP 8.28.5). Snapshot: ddev snapshot --name pre-upgrade.

Step 1 — Rework composer.json for v14

  • Bump every typo3/cms-* to ^14. Bump third-party exts (news, address, t3bootstrap/*, …) to their ^14 releases. Drop anything with no v14 release (e.g. flux/vhs are gone in v14 t3bootstrapt3bootstrap/flux-* do not exist; do not re-add them).
  • Custom-repo package name ≠ repo name. A git repo …/<name>.git may publish itself as a different composer package name — read the repo's composer.json name and require THAT.
  • Contradictory constraints fail: "^14 dev-release/v14" (tag AND branch) → composer "cannot possibly match". Use plain "^14" for a tagged package, or "dev-<branch>" for an untagged one.
  • Repository order controls the source. If the same package name is served by both a git repo and the registry, composer takes the first repo listed as canonical → list the project's git repo before the composer registry in repositories to make it win.

Step 2 — Composer auth

  • SSH git repos: ddev auth ssh (loads your keys into the DDEV agent; covers gitea@…/GitHub).
  • Private registry: ddev composer config http-basic.<registry-host> <user> <token> (writes the project auth.json). Get the token from the user — never invent/guess it.

Step 3 — Install

ddev composer update -W (resolve + install with dependencies). Fix constraint conflicts per Step 1.

Step 4 — Create config/system/settings.php

v14 does not auto-write settings.php; ConfigurationManager reads it if present, else falls back to the old typo3conf/LocalConfiguration.php in place — so a v11→v14 jump with production DB creds in the old file fails to boot first. Create settings.php from the old LocalConfiguration.php, but with DDEV DB creds (host db, name db, user db, pass db, port 3306, utf8mb4). Preserve SYS/encryptionKey, install-tool password, sitename, systemMaintainers, mail sender, and per-extension EXTENSIONS config. Drop obsolete v11 feature flags.

Step 5 — Replace public/.htaccess

Swap in the v14 template: cp vendor/typo3/cms-install/Resources/Private/FolderStructureTemplateFiles/root-htaccess public/.htaccess. Symptom of the stale v11 .htaccess: Apache 500 "Request exceeded the limit of 10 internal redirects" (looks like a config error; it's the htaccess).

Step 6 — Update the DB schema

ddev exec vendor/bin/typo3 database:updateschema "*.add,*.change" — adds the ~20 tables v12v14 need (e.g. sys_csp_resolution). Non-destructive (add/change only). Symptom if skipped: Table 'db.sys_csp_resolution' doesn't exist on any backend request.

Step 7 — Backend admin

ddev exec vendor/bin/typo3 backend:createadmin <user> <pass> (or backend:resetpassword if an admin already exists in the dump). Do NOT create admin users the user didn't ask for on shared/live systems; local DDEV is fine.

Step 8 — Upgrade wizards (⚠ snapshot first)

ddev snapshot --name pre-upgradewizards. Then vendor/bin/typo3 upgrade:list and run wizards:

  • upgrade:run gates on a "Database Up-to-Date" prerequisite → clear data blockers, re-run database:updateschema, then upgrade:run. Blockers commonly seen:
    • tx_news_domain_model_news.related_links NULL/non-int → UPDATE … SET related_links=0 WHERE related_links IS NULL OR NOT related_links REGEXP '^[0-9]+$'.
    • sys_category_record_mm duplicate PK tuples (table has no uid) → add a temp AUTO_INCREMENT PK, DELETE via self-join keeping the lowest, drop the temp column.
  • Confirmable wizards (e.g. convertHeadersize, rewriteIconPaths) error "You have to acknowledge this wizard" under --no-interaction. There is no --confirm flag (--confirm all throws). Run interactively with piped input: printf 'yes\n' | ddev exec vendor/bin/typo3 upgrade:run <id> (omit --no-interaction).
  • One-way wizards (e.g. container migration, address→contacts) — the snapshot is your only undo.
  • news 14.0.x vs FlexFormTools::cleanFlexFormXML(): older news calls it with 3 args, v14.3 wants 4 → blocks txNewsPluginUpdater. Fix: update news to a patched 14.x, or one-time-patch the vendor PluginUpdater (the wizard marks itself done; the patch is then harmless if reverted).

Step 8b — Language packs

ddev exec vendor/bin/typo3 language:update. Composer mode ships no language packs → labels fall back to English (e.g. news "more-link"). Run this whenever the site language isn't English.

Step 9 — Wire TypoScript via site sets

v14 t3bootstrap delivers TypoScript (incl. the PAGE object) through site sets, not sys_template. The site config must declare them under dependencies: in config/sites/<SITE_ID>/config.yaml, or the FE dies with "No page configured for type=0". Add the set identifiers (the name: in each ext's Configuration/Sets/*/config.yaml — independent of package name/extkey), e.g. the theme set + container-bs5-templates + news/form/address/blog/slider as used. See the design skill for details.

Step 10 — Flush + verify

ddev exec vendor/bin/typo3 cache:flush. Verify over HTTP with the correct host: /typo3/ → 200, FE with curl -H "Host: <SITE_BASE_HOST>" … (or the real DDEV URL) → 200. "No site configuration found" is usually a false alarm from curling localhost instead of <SITE_BASE_HOST>.


Other recurring traps

  • Leftover custom content element with no v14 provider → FE fatal empty-templateName InvalidTemplateResourceException. Don't assume it's flux — check the old sitepackage; a plain custom FSC CType must be re-registered for v14 (see the design skill's iconmenu example).
  • Relocating a git-repo package's vendor path: change the name in that repo's own composer.json (composer installs by declared name, not repo URL). Keep the extension-key unchanged so EXT:<key>/… refs + set names keep working. Untagged repo → require dev-<branch>.
  • v14 FlexForm relational fields: FlexFormFieldValues::get() returns a LazyRecordCollection / RecordInterface for relation fields (NOT a CSV of uids), which breaks intExplode()/(int) casts in migrated/old ext code → TypeError. Normalize to (string)/(string)$record->getUid() before parsing. Reproduce/verify from CLI with a small bootstrap script (SystemEnvironmentBuilder::run + Bootstrap::init + the record API).
  • TYPO3 Console (helhum/typo3-console ^8.3) works with v14 — vendor/bin/typo3 is the console binary.

Each step verified + a snapshot before wizards = a reversible, debuggable upgrade.