Since TYPO3 v12, you're likely aware of the introduction of the `_assets` folder in `public`. One of the major changes in composer-based TYPO3 v12 instances is the transition from `typo3conf` to `_assets`. This article provides a comprehensive guide on migrating assets such as CSS/JS, Fluid, and TypoScript.
Why did the TYPO3 core migrate typo3conf to _assets?
Because… Security Matters!
TYPO3 core has migrated from `typo3conf` to `_assets` primarily for enhanced security. With `typo3-cms-composer-installers` v4, the entire `typo3conf` directory is no longer publicly accessible. Instead, assets like CSS, JS, and images are now housed in the `_assets` folder. This change ensures that PHP, Fluid, and TypoScript files are inaccessible with TYPO3 v12 and beyond.
Did you know?
This security concepts of `typo3/cms-composer-installers` was highly inspired by the typo3-secure-web by Helmut Hummel.
What’s Security in`typo3/cms-composer-installers` >= v4?
The introduction of typo3/cms-composer-installers version 4 (compatible with TYPO3 v11) and version 5 (mandatory since TYPO3 v12.0) has eliminated the practice of saving extensions in the typo3conf folder for Composer-based projects. A detailed account of these changes can be found in the article "TYPO3 and Composer — we've come a long way." For further details, refer to the blog post titled "Composer Changes for TYPO3 v11 and v12" In this post, I outline the challenges encountered in ensuring compatibility with the new typo3/cms-composer-installers version.
The most visible change is that assets are now integrated into web pages without revealing the extension's name. In versions prior to typo3/cms-composer-installers v4, the structure resembled this:
<!-- Example from a web page using typo3/cms-composer-installers v3 --!>
<link rel="stylesheet" href="/typo3conf/ext/site_package/Resources/Public/Css/styles.css" media="all">
In contrast, starting from version 4, the typo3conf folder no longer houses extensions. Instead, the Public/Resources folders of extensions are linked within a new _assets folder under a directory identified by a hash value rather than the extension name:
<!-- Example from a web page using typo3/cms-composer-installers v4+ --!>
<link rel="stylesheet" href="/_assets/KsfKf4qImEAWgeI6dASF6e1hdBu2W/Css/styles.css" media="all">
It is crucial to note that all assets on a website now need to be stored in an extension's Resources/Public folder. Consequently, any hardcoded asset paths using typo3conf/ext must be adjusted to ensure compatibility with the updated version. Therefore, I systematically searched for typo3conf/ext paths within custom extensions of projects and gradually substituted them with the solutions outlined below. Subsequently, I included the typo3/cms-composer-installers release candidate version as a requirement in the project's composer.json file.
Deal Path Handling in Your CSS Files
Here are some tips and tricks to deal with static asset paths in your CSS files.
TYPO3 <= v11
// Wrong: The old way to include the `typo3conf` path in CSS
.CssClass {
background-image: url("/typo3conf/ext/site_package/Resources/Public/Images/TheImage.jpeg");
}
Option 1: Relative Path (TYPO3 >= v12)
// Correct: Switch assets and CSS to relative paths if they share the same extension.
.CssClass {
background-image: url("../Images/TheImage.jpeg");
}
Option 2: Embed Inline (TYPO3 >= v12)
// Small images like icons can be embedded inline using data URIs to manage assets and CSS across different extensions.
.CssClass {
background-image: url("data:image/jpeg;base64,...");
}
Option 3: Create Your Special Folder (TYPO3 >= v12)
// Storing assets in a separate public folder like public/my_assets/ simplifies referencing.
.CssClass {
background-image: url("/my_assets/Images/TheImage.jpeg");
}
Option 4: Using PSR-15 Middleware (TYPO3 >= v12)
// A PSR-15 middleware option enables streaming assets through a static URL.
.CssClass {
background-image: url("/psr15_assets/Images/TheImage.jpeg");
}
Option 5: Hard Coded Path (Not Recommend, (TYPO3 >= v12))
// Use full asset paths if assets and CSS are in different extensions and cannot be relocated, but be aware this might cause issues during TYPO3 upgrades.
.CssClass {
background-image: url("/_assets/KsfKf4qImEAWgeI6dASF6e1hdBu2W/Images/MyImage.jpeg");
}
What About Paths in JavaScript?
Data attributes
Sometimes, JavaScript code includes file references where paths are directly embedded within the script. An instance of this is seen in a Leaflet application, where custom marker icons for a map are defined.
TYPO3 <= v11
const icon = L.icon({
iconUrl: '/typo3conf/ext/site_package/Resources/Public/Icons/Map/marker.svg',
iconSize: [25, 41],
iconAnchor: [12,41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowUrl: '/typo3conf/ext/site_package/Resources/Public/Icons/Map/shadow.svg',
shadowSize: [41, 41],
});
TYPO3 >= v12
We can address this by specifying the paths to the icons within data attributes in HTML using the Fluid URI resource view helper.
<div id="map"
data-icon="{f:uri.resource(path: 'Icons/Map/marker.svg')}"
data-shadow="{f:uri.resource(path: 'Icons/Map/shadow.svg')}"
></div>
Now, we can retrieve the paths to the icons in JavaScript:
const mapElement = document.getElementById('map');
const icon = L.icon({
iconUrl: mapElement.dataset.icon,
iconSize: [25, 41],
iconAnchor: [12,41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowUrl: mapElement.dataset.shadow,
shadowSize: [41, 41],
});
Path substitution
Assuming your script is at EXT:site/Resources/Public/JavaScript/some-script.js, you can utilize document.currentScript.src to generate a correct (pseudo-relative) pathway:
const prefix = document.currentScript.src.replace(/\/JavaScript\/.*\.js.*/, '');
const image = `${prefix}/Images/some-image.png`;
When employing a JavaScript module (type="module"), you can utilize the following snippet to replace the path:
const prefix = import.meta.url.replace(/\/JavaScript\/.*\.js.*/, '');
const image = `${prefix}/Images/some-image.png`;
Webpack Encore
Take a look at Sebastian Schreiber's documentation for the typo3_encore extension to learn how to configure it to use typo3/cms-composer-installers version 4.
Please refer to Sebastian Schreiber's documentation for the typo3_encore extension to learn how to set it up with typo3/cms-composer-installers version 4.
<!-- Old way of direct add `typo3conf` path --!>
<svg><use xlink:href="/typo3conf/ext/site_package/Resources/Public/Images/icons.svg#symbol"></use></svg>
TYPO3 >= v12
<!-- The solution now is to use the Uri.resource ViewHelper <f:uri.resource> --!>
<link href="{f:uri.resource(path:'Css/MyCss.css')}" rel="stylesheet" />
Option 1: Best practice with EXT: syntax
<!-- Syntax --!>
<link href="{f:uri.resource(path:'EXT:site_package/Resources/Public/Css/MyCss.css')}" rel="stylesheet" />
<!-- Output --!>
<link href="typo3/sysext/site_package/Resources/Public/Css/MyCss.css" rel="stylesheet" />
Option 2: Default
<!-- Syntax --!>
<link href="{f:uri.resource(path:'Css/MyCss.css')}" rel="stylesheet" />
<!-- Output --!>
<link href="typo3conf/ext/site_package/Resources/Public/Css/MyCss.css" rel="stylesheet" />
Option 3: With extension name
<!-- Syntax --!>
<link href="{f:uri.resource(path:'Css/MyCss.css', extensionName: 'OtherExtension')}" rel="stylesheet" />
<!-- Output --!>
<link href="typo3conf/ext/other_extension/Resources/Public/Css/MyCss.css" rel="stylesheet" />
<?php
declare(strict_types=1);
namespace Acme\Site\Provider;
use TYPO3\CMS\Core\Utility\GeneralUtility;
final class LogoProvider
{
private const PATH_LOGO = 'typo3conf/ext/site/Resources/Public/Images/logo.png';
public static function getLogo(): string
{
return GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . self::PATH_LOGO;
}
}
TYPO3 >= v12
<?php
declare(strict_types=1);
namespace Acme\Site\Provider;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
final class LogoProvider
{
private const PATH_LOGO = 'EXT:site/Resources/Public/Images/logo.png';
public static function getLogo(): string
{
return rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/')
. PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName(self::PATH_LOGO));
}
}
Manipulate Asset in TypoScript
Still, many people use TypoScript to add assets like fonts, images, and css/js files.
// In TypoScript, Example of adding path using EXT:name.
page.headerData.10 = TEXT
page.headerData.10 {
value = <link rel="preload" href="{path : EXT:site/Resources/Public/Fonts/Roboto.woff2}" as="font" type="font/woff2" crossorigin="anonymous">
insertData = 1
}
// Another example is OpenGraph image is an absolute URL in meta tags:
page.meta {
og:image.cObject = TEXT
og:image.cObject {
typolink {
parameter.cObject = IMG_RESOURCE
parameter.cObject.file = EXT:site/Resources/Public/Images/opengraph.png
returnLast = url
forceAbsoluteUrl = 1
}
}
}
TYPO3 <= v11
<INCLUDE_TYPOSCRIPT: source="FILE:typo3conf/ext/site/Configuration/TSconfig/User/name.tsconfig">
TYPO3 >= v12
<INCLUDE_TYPOSCRIPT: source="FILE:EXT:site_package/Configuration/TSconfig/User/name.tsconfig">
Use “EXT:” for RTE Configuration
Do you want to include your custom configuration e.g.., Add CSS?
TYPO3 <= v11
editor:
config:
contentCss: '/typo3conf/ext/site_package/Resources/Public/Css/rte.css'
TYPO3 >= v12
editor:
config:
contentCss: 'EXT:site/Resources/Public/Css/rte.css'
What About Static Files?
You can create a custom middleware to generate the content of a static file like manifest.webmanifest. Another option is to store assets externally from TYPO3, such as in your web root or a dedicated static directory like static/.
How is the TYPO3 hash created?
The hash is generated in TYPO3 versions 11 and 12 using the method \TYPO3\CMS\Core\Utility\PathUtility->getAbsoluteWebPath().
Starting from the full file path of an asset, such as /var/www/html/vendor/site_vendor/site_package/Resources/Public/Css/Custom.css, the composer root path (in this case /var/www/html) and the segment beginning with Resources/Public are removed. What remains (in our example /vendor/site_vendor/site_package/) is then hashed using md5.
Thus, the hash remains consistent unless modifications are made to the PathUtility method or changes to the vendor folder in the project's composer.json file. It is strongly recommended that the methods above be utilized to generate asset paths and avoid hardcoding them.
Wrap-up!
You should be proud of TYPO3, as it has become more secure with composer mode. In this mode, your extension's code is no longer publicly available; only assets are accessible.
The `typo3/cms-composer-installers` package stores both core and custom extensions in the `vendor folder`.
The `Resources/Public` folders of these extensions are then linked into the `_assets` folder in the web root, with a hashed folder name to obscure the extension name.
When upgrading to TYPO3 version 12 or higher in composer mode, ensure you update the paths to your custom extension code, including CSS/JS and path setups in PHP, Fluid, TypoScript, and RTE..
Have a Happy Migration to _assets!
This blog post was really helpful for moving my TYPO3 setup to Composer in version 12. The guide on shifting assets from typo3conf to _assets was clear and easy to understand. The instructions and tips for handling CSS, JS, Fluid, and TypoScript were great. I recommend this for anyone migrating to TYPO3 v12!
The step-by-step guide and clear examples made the transition much easier to understand. I appreciate the emphasis on security and how the new structure improves it.One question I have is about the best practices for managing assets in a multi-extension setup—are there any specific strategies you recommend for that?
Thank you for this comprehensive guide. It made the migration process much less intimidating. Our TYPO3 instance is running perfectly now. Can you recommend any tools to automate part of this migration?
I was struggling with this migration until I found your blog. The detailed explanations and examples were exactly what I needed.