You can now become a hero and support this Blog at GitHub Sponsors or Patreon.

More about support

TYPO3 undertakes the next big step in its capabilities and functionality as a Composer based application. With TYPO3 v11 (optional) and TYPO3 v12 (mandatory) the structure of Composer based TYPO3 projects will change. And it will change for the better.

We are saying "Good Bye" to old friends like typo3conf/ext and old enemies like the accidental sharing of nonpublic extension files. The web root of a Composer based TYPO3 project will look different and overall web security will increase. Or, as Benni Mack puts it:

 We've worked together over the past seven years to achieve this milestone for TYPO3.

(source: TYPO3 and Composer — we've come a long way, Blog Post, 2022)

Before we take a closer look, let's take some moments and look back at how we got here.

Previously on "TYPO3 and Composer"

TYPO3 and Composer have come a long way. I covered the basics way back in 2015 for TYPO3 v7 and v8 with the article TYPO3 and Composer. While some of the things stated there are still valid, many things have further improved and thus, changed. For example the Composer package typo3/cms is long gone now.

That is, because a big step was taken during TYPO3 v9 times with the so called "subtree split", that made every TYPO3 system extension available as a separate git repository and therefore also as a Composer package (read more about this in the article The TYPO3 Subtree Split and Composer). Also, a policy was communicated to move all extensions to to get rid of

As of February 2022, there are about 2.700 packages registered as publicly available TYPO3 extensions (type:typo3-cms-extension) at packagist. Impressive.

The composer plugin typo3/cms-composer-installers did most of the heavy lifting for composer based TYPO3 projects since the early days. It detected TYPO3 extensions and made sure they ended up in the typo3conf/ext folder. It also assured that system extensions and entry point scripts like index.php are at the correct places.

It is the reason we put the following in the composer.json files of our extensions:

 "extra": {
	"typo3/cms": {
		"extension-key": "my_extension"

For Composer based TYPO3 installations this is a central piece of software, that is constantly improved and quite well maintained. Kudos to Helmut Hummel, who put a lot of thoughts and work into this package as well as the whole area "TYPO3 and Composer" for years. Without him, TYPO3 for sure would not be where it is today in terms of composer support. So, thank you, Helmut <3!

When TYPO3 v11 LTS was released, typo3/cms-composer-installers was required by the TYPO3 core with version 2 or version 3 (^2.0 || ^3.0, see git tag 11.5.0 at GitHub for reference). But v4 of the package was already at the horizon.


typo3/cms-composer-installers v4

During the development of TYPO3 v12, support for v4 of the composer-installers has been added. But more interestingly, the before mentioned v4 support has also been backported to the 11.5 branch of TYPO3 with this Patch.

Since then, the composer constraint for typo3/cms-composer-installers looks like this for the TYPO3 core:

"typo3/cms-composer-installers": "^2.0 || ^3.0 || ^4.0",

And this means, that once v4 of the composer-installers is released, a regular composer update 'typo3/*' -W command will update the package to v4. Be aware of this when you update TYPO3 11 LTS to a new minor patch level and update all dependencies (-W) as you should.

To say this one more time:

Once v4 of the composer-installers is released, a regular composer update 'typo3/*' -W command will update the package to v4.

We will see what that actually means in a minute, for now let's make sure that everybody is on the same page for this, or as Helmut put it:


With Helmut asking everybody to spread the word (original tweet), I did my part in saying it twice and now, one more time remind you to read the blue box above.

OK, enough with the word spreading.

Let's finally see why we WANT v4 of the typo3/cms-composer-installers, shall we?


A new structure for composer based TYPO3 projects

With v4 of typo3/cms-composer-installers the structure of the publicly available folder (aka document root, aka web root) will change quite a bit. All extensions will be installed into the Composer vendor folder like every other package loaded by Composer. Extensions will no longer be put or symlinked to typo3conf/ext.

In fact, there will no longer be a typo3conf/ext folder.

Instead, all public files of all installed extensions are symlinked into a new folder called _assets. The extension name is replaced by a hash that can not be translated back to an extension name. Each hash is a symlink to the Resources/Public folder of the corresponding extension.

All other files are no longer accessible through the web.

And this is the big gain. Security by design.

No more unintended file leakages due to insufficient webserver configuration. Also, it is now much harder to identify installed extensions, because a path to a public asset loaded from extensions does no longer contain the extension name but the before mentioned hash.

If this reminds you of Helmuts typo3-secure-web package (see at GitHub), you are absolutely right. It is a very similar functionality.

To illustrate what we just said, let's have a look at the resulting folder structure of an example project with a few installed extensions.

The public web root folder looks like this:

tree public -L 3

├── _assets
│   ├── 081fa96a07de1dccb64a8a83e1567439 -> ../../vendor/typo3/cms-backend/Resources/Public
│   ├── 0c22fd3d95c93dc31098cc8d4b1d7232 -> ../../vendor/typo3/cms-belog/Resources/Public
│   ├── 14525e5262e0d22d75f200ddba75ef39 -> ../../vendor/typo3/cms-fluid-styled-content/Resources/Public
│   ├── 1c7427e903b0a6ef0b4495f72da0f918 -> ../../vendor/b13/host-variants/Resources/Public
│   ├── 1ee1d3e909b58d32e30dcea666dd3224 -> ../../vendor/typo3/cms-core/Resources/Public
│   ├── 2983062c4310b9f778ca0a360e9a8c05 -> ../../vendor/typo3/cms-recordlist/Resources/Public
│   ├── 2a58d7833cb34b2a67d37f5b750aa297 -> ../../vendor/typo3/cms-frontend/Resources/Public
│   ├── 2f9f1781442d461eab73b573487122ef -> ../../vendor/typo3/cms-form/Resources/Public
│   ├── 34d632057b1a1586dba4464836293c82 -> ../../vendor/typo3/cms-viewpage/Resources/Public
│   ├── 37ee5ba7a97ad859ac99cb8181e0c0e3 -> ../../vendor/typo3/cms-dashboard/Resources/Public
│   ├── 509a7bacf53455dd0e8bc14336b58c3d -> ../../vendor/typo3/cms-extbase/Resources/Public
│   ├── 6104b7f46d656dcf567ead154886bf37 -> ../../vendor/typo3/cms-install/Resources/Public
│   ├── 655b24546496542a156e68040db1973b -> ../../vendor/b13/bolt/Resources/Public
│   ├── 656f1405e34da8aeed0d2793b00aa50a -> ../../vendor/typo3/cms-felogin/Resources/Public
│   ├── 6942460bf1dad5e61e3275534adcf333 -> ../../vendor/typo3/cms-sys-note/Resources/Public
│   ├── 8a66752868b3228c5bb3123a8e469f95 -> ../../vendor/danielgoerz/dummy-plugin/Resources/Public
│   ├── 92d0321322c294e77404bf74cad9c7d4 -> ../../vendor/typo3/cms-beuser/Resources/Public
│   ├── 937be57c7660e085d41e9dabf38b8aa1 -> ../../vendor/typo3/cms-rte-ckeditor/Resources/Public
│   ├── 984e6ee9829f85eb447bb6a36455204a -> ../../vendor/typo3/cms-seo/Resources/Public
│   ├── 9e592a1e5eec5752a1be78133e5e1a60 -> ../../vendor/typo3/cms-setup/Resources/Public
│   ├── a3db3388ff457cff602f74c897474ebb -> ../../vendor/typo3/cms-fluid/Resources/Public
│   ├── b1439bab8f20d76bfa926e2ce031de9d -> ../../vendor/typo3/cms-info/Resources/Public
│   ├── c8e1f76a9cd1146509b84ed6e77f8f60 -> ../../vendor/typo3/cms-t3editor/Resources/Public
│   ├── cddd1dcdf2ba9efa5a1cbf7e00411594 -> ../../vendor/typo3/cms-extensionmanager/Resources/Public
│   ├── eecf5c3877e98efe7b03503e2b450a69 -> ../../vendor/typo3/cms-filelist/Resources/Public
│   ├── faac6516a43fc0a1b5f92bf624a4e449 -> ../../vendor/typo3/cms-tstemplate/Resources/Public
│   └── faf19450e1010c05b9936b975b1457a6 -> ../../vendor/typo3/cms-impexp/Resources/Public
├── fileadmin
│   ├── _temp_
│   │   └── index.html
│   └── user_upload
│       ├── _temp_
│       └── index.html
├── index.php
├── typo3
│   ├── index.php
│   └── install.php
├── typo3conf
│   └── LocalConfiguration.php
└── typo3temp
    ├── assets
    │   ├── _processed_
    │   ├── compressed
    │   ├── css
    │   ├── images
    │   └── js
    └── index.html

We see that all extensions, i.e. local extensions (EXT:dummy_plugin), 3rd party extensions (EXT:bolt, EXT:host_variants) and system extensions (everything else) have their public assets symlinked into the _assets folder. The extension names have been obscured and all PHP files are located in the vendor folder outside the publicly accessible document root.

The typo3 folder contains nothing but the entry scripts index.php and install.php.

The typo3conf folder is also almost empty. The configuration files LocalConfiguration.php and AdditionalConfiguration.php (absent in the example but if the file existed, it would still be located in the folder typo3conf) still live here, but they might vanish from the publicly available folders as well until the release of TYPO3 v12 LTS.

Now, that we know why we absolutely want this new structure as soon as possible, let's look on how to deal with potential issues and how to prepare a project to have a smooth transition.


Potential Issues and how to solve them

Some things should be considered before updating the composer-installers in a project.

There also is the option to opt-out of the update for the time being if time, project structure or code base do not allow for a quick update.

Let's have a look.


Omitting the new behavior completely in TYPO3 v11 projects

First of all, there is a simple thing you can do, if you do not want to deal with these changes at all and rather look at them in a future TYPO3 update to v12, when this becomes the new standard.

In that case, just tell Composer to not update to v4 of the typo3/cms-composer-installers package through your projects root composer.json and you are done. One command should do the trick:

composer req typo3/cms-composer-installers:^3

With this v4 will not be installed once it is released.


Load local extensions with composer

Most projects contain at least one local extension, developed merely for the projects it lives in, most likely in the same git repository. Make sure that those extensions are loaded by composer as well. This can be achieved by using a path repository configuration in the root composer.json of the project:

"repositories": [
        "type": "path",
        "url": "src/extension/*"

All extensions have to be moved to the specified path, and they also mus contain a valid composer.json file. You can also have multiple path repositories, if you somehow need more than one path.

You probably already do this, so let's move to issues, that might come up.


Remove hardcoded paths containing typo3conf/ext

Depending on your code base and deployment process this might be the biggest workload. Scan your project for occurrences of typo3conf/ext. Here are some, but certainly not all, possible places where someone could have used the path:

  • Specifying some sort of image file path in a fluid template or in typoscript
  • Including JavaScript or CSS with the full path
  • Pointing to images or fonts from CSS (think background-image or icon fonts)
  • Building assets to Resources/Public of an extension during the deployment process

Note that the EXT: syntax is perfectly fine wherever it is resolved by the TYPO3 core. In fact, it is often the correct way to specify any assets:

page.includeCSS {
  veryGoodStyles = EXT:my_ext/Resources/Public/CSS/veryGoodStyles.css

This will still work.

If for any reason you need to include a file in a fluid template, use the ResourceViewHelper to resolve the path for you:

<link href="{f:uri.resource(path:'Css/veryGoodStyles.css', extensionName: 'myExt')}" rel="stylesheet" />

When looking for occurrences of hard coded typo3conf/ext paths, also look for usages of the environment API for that path, as that API may yield the old and now absent path as well:

// this will yield typo3conf/ext as well

On a side note: referencing nonpublic files with the EXT: syntax also still works, nothing has to be done in case like the following:

page.inlineLanguageLabelFiles {
    veryGoodLabels = EXT:my_ext/Resources/Private/Language/locallang.xlf

Last but not least, also make sure, that no extension provides publicly available assets from a different path than Resources/Public. They absolutely shouldn't, but better be safe than sorry.

And with all that said and considered, you should be able to get a pretty good idea of the efforts you have to put into moving to the new and superior structure in Composer based TYPO3 projects. It is absolutely possible that you do not need to do anything, but a few hours of work are likely. Anyway, it is 100% worth the effort.

If you encounter any further issues please let the core team know by opening issues at forger or GitHub.

Thanks for reading and for supporting the blog!

Further Information