
Hi folks,
My own website hoja.in ran on TYPO3 7.6 for a very long time. Classic mode, no Composer, flux and fluidpages for the page templates, realurl for the URLs and powermail for the contact form. The blog lived on a separate subdomain with WordPress 5.6. Funny situation: I upgrade TYPO3 installations for a living, and my own site was the oldest one I touched.
In 2026 I finally moved it to TYPO3 14.3. This post is the overview: what I decided, in which order I worked, and the commands that started it. Every bigger step has its own post, linked at the end.
Problems I faced
When I listed what was installed, the picture was clear quickly:
- Classic mode with the core in git. The 7.6 source was committed into the repository. When I booted a copy for testing, the committed
index.phpwas empty, so I had to take the file from the originaltypo3_src-7.6.10.zip. - Extensions without a future. flux, fluidpages, fluidcontent, vhs, realurl and builder. realurl has had no role since TYPO3 9.5 brought site handling and slugs into the core. powermail and pxa_cookie_bar had no v14 release.
- Own extensions. The site package (extension key
templates) depended on flux, and an oldsmsloginextension was still installed. - The hop chain. A classic in-place upgrade means 7.6 → 8.7 → 9.5 → 10.4 → 11.5 → 12.4 → 13.4 → 14.3. Seven hops, each with its own PHP version, its own upgrade wizards and its own broken templates.
Why a fresh install beat seven hops
For a big installation with many editors, workspaces and thousands of records, I usually stay with the in-place path, because the upgrade wizards migrate data that you cannot easily create again. hoja.in is the opposite case. The final import was 19 pages, 10 content elements and 6 file references. The templates were going to be thrown away anyway, because flux does not come to v14 with me.
So walking seven hops would mean carrying flux structures through seven sets of wizards only to delete them at the end. A fresh TYPO3 14.3 Composer project plus a small import script was simply less work and less risk. That was the main decision, and everything else followed from it.
The plan, step by step
Step 1: Inventory before opinions
Before deciding anything, I checked which extension was really used on which page. Two findings saved work. The smslogin extension had no plugin instance on any page of the live database, so it was dropped (the code stays in the git history). A flux carousel in a special column (colPos 18181) never rendered on the live site at all, so it was not imported. A hidden demo page tree from the original one-page theme was also left behind.
Step 2: A smoke baseline from the old site
I booted the old 7.6 site in a separate DDEV project with PHP 7.0 and MariaDB 10.5, loaded the live dump, and captured seven URLs: HTTP status, final URL, size, page title and whether an error marker appeared. The HTML of each page was saved too. Without this baseline, “it looks fine” is only a feeling. With it, every later change can be compared against what the old site really delivered.
Step 3: A fresh Composer project with DDEV
For a brand new project, these are the commands to start from the base distribution:
# DDEV project: TYPO3 type, web root public/, PHP 8.4, MariaDB 10.11
ddev config --project-type=typo3 --docroot=public --php-version=8.4 --database=mariadb:10.11
ddev start
# TYPO3 14.3 from the base distribution, then the CLI setup
ddev composer create-project "typo3/cms-base-distribution:^14.3"
ddev exec vendor/bin/typo3 setupIn my case the git repository already existed, so I turned it into a Composer root myself: web root public/, own extensions under Source/Extension/ as a path repository, and the PHP platform pinned so that the lock file matches the server:
ddev composer config repositories.local path 'Source/Extension/*'
ddev composer config platform.php 8.4
ddev composer require "typo3/cms-core:^14.3" "typo3/cms-fluid-styled-content:^14.3"
"typo3/cms-form:^14.3" "typo3/cms-seo:^14.3" "typo3/cms-redirects:^14.3"
"harvisoft/sitepackage:@dev"The list above is shortened; the real composer.json also requires the backend modules I use, like filelist, info, lowlevel and tstemplate.
Step 4: A new site package
The old templates extension became sitepackage, rebuilt without flux and vhs. It ships a site set (harvisoft/sitepackage) that depends on fluid-styled-content, form and the SEO sitemap set, and it renders the page with PAGEVIEW. The site configuration lists the set as a dependency, so no sys_template record is needed.
Step 5: Import the content with a script
A PHP script boots TYPO3 on the CLI, reads the old database (loaded into a second DDEV database called legacy) and writes pages, content and file references with their original uids. Slugs are generated with the core SlugHelper, the powermail element becomes an EXT:form element, and old image paths are rewritten to fileadmin/. It is re-runnable: it clears what it imported before it starts.
One v14 detail bit me here: the field pages.url does not exist any more. Breaking change #17406 renamed the “External URL” page type to “Link”, which uses the typolink field pages.link. My blog menu item was such a page, so the script writes /blog/ into link.
Step 6: Replace each extension
- realurl → site configuration and slugs in the core.
- powermail → the core form framework with one
contact.form.yaml. - flux and fluidpages → backend layouts in page TSconfig of the set, plus
PAGEVIEWtemplates. - vhs → core ViewHelpers and data processors (menu and files).
- pxa_cookie_bar → dropped, because TYPO3 sets no tracking cookies on this site.
Step 7: Move the blog
WordPress went from 5.6 to 7.1 and from blog.hoja.in into public/blog, served at hoja.in/blog. The old subdomain gets a 301 to the new path.
Step 8: The gate
Done means measured. The seven baseline URLs had to answer 200 without error markers, the new speaking URLs and /blog/ as well, upgrade:list had to show no pending wizards, and a contact form submission had to arrive in Mailpit. Only then I called the migration finished.
What I would tell you before you try the same
A fresh install is a strong option when the content is small and the templates are going away anyway. It is a bad option when editors depend on history, workspaces or many translated records. Decide with numbers from your own database, not with the hope that “it is only a small site”.
The deeper posts:
- Building a TYPO3 v14 site package with site sets and PAGEVIEW
- Importing content from TYPO3 7.6 into v14 with a script
- Replacing powermail with EXT:form in TYPO3 v14
- A smoke test baseline before you touch a TYPO3 upgrade
- Upgrading a WordPress blog from 5.6 to 7.1 on PHP 8
Feedbacks are the champion’s breakfast, so drop a comment if you are planning a similar jump.