--- name: typo3-v11-to-v14-ddev-upgrade description: "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)." metadata: author: Wappler version: "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 - `` — the DDEV project root (docroot `public/`). - `` — the site identifier folder under `config/sites//`. - `` — the site's base host from `config/sites//config.yaml` (e.g. the `*.ddev.site` host). The FE only answers on this host, NOT `localhost`. - `` — the private Composer registry for the theme/vendor packages, if any (e.g. a Gitea `.../api/packages//composer` URL). `` — any `gitea@`/GitHub SSH git repos in `composer.json` `repositories`. - `` — the sitepackage's composer `name` (⚠ often ≠ repo name — read the repo's own `composer.json`). `` — 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.2–8.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 t3bootstrap** — `t3bootstrap/flux-*` do not exist; do not re-add them). - **Custom-repo package name ≠ repo name.** A git repo `…/.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-"` 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. ` (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 v12–v14 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 ` (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 ` (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//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: " …` (or the real DDEV URL) → 200. **"No site configuration found" is usually a false alarm from curling `localhost`** instead of ``. --- ## 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:/…` refs + set names keep working. Untagged repo → require `dev-`. - **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.