TYPO3 _assets: How to Migrate typo3conf/ext Assets in Composer

TYPO3 _assets: How to Migrate typo3conf/ext Assets in Composer

Moving a TYPO3 project to Composer changes more than the way extensions are installed. It also changes how public extension assets such as CSS, JavaScript, images and fonts are exposed to the web.

If your project still contains hardcoded paths such as:

 

/typo3conf/ext/site_package/Resources/Public/...

 

you need to review them when migrating to a modern Composer-based TYPO3 installation.

In Composer mode, TYPO3 installs extensions outside the public web root. Their public resources are exposed through public/_assets/, while files such as PHP classes, configuration and templates remain protected from direct HTTP access. 

TYPO3 v12 requires typo3/cms-composer-installers v5, making this structure part of the standard Composer setup.

The good news: you normally should not replace old URLs with hardcoded _assets URLs. Instead, use TYPO3's resource APIs, EXT: references, relative paths, or your frontend build system.

This guide shows exactly how to migrate.

What is _assets in TYPO3?

public/_assets/ contains symlinks to the Resources/Public/ directories of Composer-installed TYPO3 extensions.

For example:

 

packages/site_package/
└── Resources/
    └── Public/
        ├── Css/
        ├── JavaScript/
        ├── Images/
        └── Fonts/

 

Composer/TYPO3 exposes these public resources through:

public/_assets/<generated-hash>/

The hash helps avoid exposing the extension's name and Composer path. However, the exact hashing mechanism is an implementation detail and may change in future TYPO3 major versions.

Therefore: don't build your application around a hardcoded _assets/<hash>/... URL.

Why did TYPO3 change from typo3conf/ext to _assets?

The main reason is security and a cleaner Composer architecture.

In older installations, an extension could be directly accessible below:

 

public/typo3conf/ext/

 

That meant the web server could potentially expose files that were never intended to be public.

Modern Composer installations place extension packages outside the public web root, typically under vendor/ or a local packages/ directory. Only the extension's Resources/Public/ directory is exposed to the browser.

The basic architecture is now:

 

Composer extension
      │
      ├── PHP
      ├── Configuration
      ├── Templates
      └── Resources/Public
                    │
                    ▼
              public/_assets/

 

This creates a much better separation between application code and public assets.

typo3conf/ext vs Composer _assets

Older approachComposer-based TYPO3
public/typo3conf/ext/vendor/ or packages/
Extension code publicly locatedExtension code outside web root
Assets referenced directlyPublic assets exposed through _assets
Hardcoded extension paths commonTYPO3 resource APIs preferred
Higher risk of exposing internal filesBetter public/private separation

TYPO3's current directory structure documentation confirms that public/_assets/ contains symlinks to extension Resources/Public/ directories and that typo3conf/ext/ is no longer used in modern Composer projects.

Step 1: Find old typo3conf/ext references

Before changing code, search your complete project for:

 

typo3conf/ext/

 

Check:

  • Fluid templates
  • CSS
  • SCSS
  • JavaScript
  • TypeScript
  • PHP
  • TypoScript
  • TSconfig
  • RTE configuration
  • YAML
  • JSON
  • frontend build configuration
  • custom scripts

TYPO3 itself recommends searching extension code for old typo3conf/ext/ references during the migration.

A simple project-wide search can reveal most migration candidates:

 

grep -R "typo3conf/ext/" .

 

Your goal is not to blindly replace every occurrence. First identify what kind of path it is.

Step 2: Keep public assets in Resources/Public

Public extension assets should live inside:

 

Resources/Public/

 

Typical structure:

 

Resources/Public/
├── Css/
├── JavaScript/
├── Images/
├── Fonts/
└── Icons/

 

TYPO3 defines Resources/Public specifically for files that should be delivered by the web server, such as CSS, JavaScript, images and fonts.

Private resources such as templates, configuration and localization files should remain private.

Migrating CSS asset paths

One of the most common migration problems is a hardcoded CSS URL.

Old approach

 

.CssClass {
    background-image: url("/typo3conf/ext/site_package/Resources/Public/Images/TheImage.jpeg");
}

 

This path will not work correctly once the extension is installed through the modern Composer structure.

Best option: use relative paths

If the CSS and image belong to the same extension, use a relative URL:

 

.CssClass {
    background-image: url("../Images/TheImage.jpeg");
}

 

This is the preferred approach for resources within the same extension. TYPO3's migration documentation specifically recommends relative links for this scenario.

Avoid this

 

background-image: url("/_assets/<hash>/Images/TheImage.jpeg");

 

Even though a hashed _assets URL may work, it couples your CSS to TYPO3's internal asset mapping.

What if CSS references an asset from another extension?

This is a more complicated case.

For example:

Extension A

 

└── Resources/Public/Css/style.css

 

Extension B

 

└── Resources/Public/Images/logo.svg

 

CSS cannot conveniently use TYPO3's EXT: syntax directly in the browser.

Better options include:

  1. Move shared assets into a central sitepackage.
  2. Use a frontend asset bundler such as Vite, webpack, Gulp or Encore.
  3. Generate the asset URL through Fluid, PHP or TypoScript and pass it into the frontend.
  4. Use a dedicated route or PSR-15 middleware for special dynamic asset requirements.

TYPO3 recommends centralizing shared assets or using a bundler where appropriate.

Migrating JavaScript asset paths

JavaScript often contains hidden asset dependencies.

For example, a map library may contain:

 

const icon = L.icon({
    iconUrl: '/typo3conf/ext/site_package/Resources/Public/Icons/Map/marker.svg',
    shadowUrl: '/typo3conf/ext/site_package/Resources/Public/Icons/Map/shadow.svg'
});

 

Instead of hardcoding the old path, let TYPO3 resolve the resource.

Recommended approach: pass URLs through HTML

In Fluid:

 

<div
    id="map"
    data-icon="{f:uri.resource(path: 'Icons/Map/marker.svg')}"
    data-shadow="{f:uri.resource(path: 'Icons/Map/shadow.svg')}"
></div>

 

Then JavaScript can consume the generated URLs:

const mapElement = document.getElementById('map');

 

const icon = L.icon({
    iconUrl: mapElement.dataset.icon,
    shadowUrl: mapElement.dataset.shadow
});

 

This approach keeps TYPO3-specific path resolution in TYPO3 and keeps your JavaScript independent of _assets.

TYPO3's current Resources API recommends this kind of resource resolution rather than referencing _assets directly.

Migrating Fluid templates

Fluid templates should use TYPO3's resource handling instead of hardcoded typo3conf/ext URLs.

Old

 

<img src="/typo3conf/ext/site_package/Resources/Public/Images/logo.svg" alt="Logo">

 

Recommended

 

<img
    src="{f:uri.resource(path: 'Images/logo.svg')}"
    alt="Logo"
>

 

For an SVG sprite, for example:

 

<svg>
    <use href="{f:uri.resource(path: 'Images/icons.svg')}#symbol"></use>
</svg>

 

You can also explicitly reference an extension using EXT: where appropriate:

 

{f:uri.resource(
    path: 'EXT:site_package/Resources/Public/Images/logo.svg'
)}

 

TYPO3's Resources API identifies f:uri.resource as the standard Fluid mechanism for resolving public extension resources.

Best practice

Let Fluid/TYPO3 generate the public URL.

Do not try to calculate the _assets hash yourself.

Migrating PHP asset references

PHP can require either a filesystem path or a public web URL. These are two different things.

For a server-side file path, TYPO3 supports the EXT: syntax with its resource/path APIs:

 

$absoluteFilePath = GeneralUtility::getFileAbsFileName(
    'EXT:site_package/Resources/Public/Images/logo.png'
);

 

For a public URL, use an API intended to resolve a web path rather than manually concatenating:

 

TYPO3_SITE_URL + /_assets/<hash>/...

 

TYPO3's current Resources API explicitly distinguishes server-side resource paths from public resource URLs.

Remember

Filesystem path ≠ Public URL

This distinction prevents many Composer migration bugs.

Migrating TypoScript asset references

TypoScript frequently contains references to CSS, JavaScript, fonts and images.

Use EXT: references instead of the old filesystem path.

For example:

 

page.meta {
    og:image.cObject = TEXT
    og:image.cObject {
        typolink {
            parameter.cObject = IMG_RESOURCE
            parameter.cObject.file =
                EXT:site_package/Resources/Public/Images/opengraph.png
            returnLast = url
            forceAbsoluteUrl = 1
        }
    }
}

 

The same principle applies when defining resources such as fonts or other public assets.

TYPO3's migration documentation recommends EXT:my_extension/Resources/Public/... notation where supported.

Migrating TSconfig

Old TSconfig references may look like:

 

<INCLUDE_TYPOSCRIPT: source="FILE:typo3conf/ext/site/Configuration/TSconfig/User/name.tsconfig">

 

Use the extension-aware syntax instead:

 

<INCLUDE_TYPOSCRIPT:    source="FILE:EXT:site_package/Configuration/TSconfig/User/name.tsconfig">

 

This removes the dependency on the old typo3conf/ext location.

Migrating RTE configuration

RTE configuration is another easy-to-miss location.

Old

 

editor:
  config:
    contentCss: '/typo3conf/ext/site_package/Resources/Public/Css/rte.css'

 

Recommended

 

editor:
  config:
    contentCss: 'EXT:site_package/Resources/Public/Css/rte.css'

 

When migrating a TYPO3 project, include RTE configuration in your project-wide search for typo3conf/ext.

What about static files?

Not every file belongs in Resources/Public.

If a file needs a predictable URL directly below the public web root, such as a project-level static resource, you may need a dedicated public directory or another TYPO3 mechanism.

For dynamic content, consider:

  • PSR-15 middleware
  • dynamic routes
  • a dedicated public endpoint
  • a project-level public directory

TYPO3's migration documentation specifically recommends dynamic routes, middleware or custom public directories for static links that cannot use the normal extension resource mechanism.

Also remember:

Do not put runtime-generated files into Resources/Public.

That directory is intended for static extension assets.

Don't manually manage public/_assets

This is one of the most important rules.

The _assets directory is generated and managed as part of the Composer/TYPO3 setup.

Do not:

  • manually rename its directories
  • manually replace its symlinks
  • commit production _assets contents as normal files
  • build application logic around the generated hash

TYPO3 recommends that _assets remains reproducible and that its symlinks can be recreated through Composer. A composer dumpautoload can recreate missing public-asset links after resources are added.

How do I find the _assets URL?

If you genuinely need to inspect the generated asset mapping, TYPO3 Console provides:

 

vendor/bin/typo3 frontend:asseturl

 

This can help identify the public resource directory hash for installed extensions.

But use this primarily for debugging or special cases, not as your normal asset-reference strategy. TYPO3 recommends avoiding direct _assets references because the hashing mechanism is an implementation detail.

TYPO3 _assets migration checklist

Before finishing your migration, check:

  • Search the complete project for typo3conf/ext/.
  • Move public extension assets into Resources/Public/.
  • Replace hardcoded Fluid URLs with f:uri.resource.
  • Use EXT: references in TypoScript and supported configuration.
  • Replace old CSS paths with relative URLs where assets belong to the same extension.
  • Pass TYPO3-generated URLs to JavaScript through data attributes when necessary.
  • Review PHP filesystem paths separately from public URLs.
  • Check TSconfig and RTE configuration.
  • Review frontend build pipelines.
  • Avoid hardcoding _assets/<hash>/.
  • Do not manually manage _assets symlinks.
  • Run composer dumpautoload when required.
  • Clear caches and test the frontend.
  • Test CSS, JavaScript, images, fonts, SVGs and downloads.
  • Test production deployment with a clean Composer install.

Common TYPO3 _assets migration mistakes

Replacing every old path with _assets

Don't simply turn:

 

/typo3conf/ext/site/Resources/Public/logo.svg

 

into:

 

/_assets/<hash>/logo.svg

 

Calculating the hash yourself

The hash is an implementation detail.

Using filesystem paths as browser URLs

A server-side path and a public URL are not interchangeable.

Forgetting frontend build tools

Webpack, Vite, Encore, Gulp and similar pipelines may still contain old output paths.

Editing _assets manually

Let Composer/TYPO3 create the symlinks.

Final takeaway

The move from typo3conf/ext to Composer-based extension handling is more than a path change. It is a shift toward a cleaner separation between private extension code and public web assets.

The most important rule is simple:

Don't migrate old paths by hardcoding _assets URLs. Migrate the way your application references assets.

Use:

  • Resources/Public/ for static public extension assets
  • f:uri.resource in Fluid
  • EXT: notation where supported
  • relative paths for assets within the same extension
  • TYPO3 resource APIs for PHP
  • data attributes for JavaScript when URLs need to be passed into frontend code
  • a central asset strategy or bundler for shared assets

Once these patterns are in place, your TYPO3 project becomes more secure, Composer-friendly and easier to maintain across future TYPO3 upgrades.

public/_assets/ contains symlinks to the Resources/Public directories of Composer-installed extensions. It allows public assets to be served without exposing the complete extension code.

Modern Composer-based TYPO3 installations place extensions outside the public web root. This improves security by exposing only resources intended for public delivery.

Generally, no. Use TYPO3's resource APIs, EXT: notation, relative URLs or your frontend build system instead.

For extensions, place static public resources in:

 

Resources/Public/

 

Typical subdirectories include Css, JavaScript, Images and Fonts.

Search your project for typo3conf/ext/, then migrate each reference according to its context: Fluid, CSS, JavaScript, PHP, TypoScript, TSconfig, RTE or frontend build configuration.

Your One-Stop Solutions for Custom TYPO3 Development

Discover custom TYPO3 development solutions from T3Planet Shop, tailored to your project, business goals, and technical requirements.

  • A Decade of TYPO3 Industry Experience
  • 350+ Successful TYPO3 Projects
  • 87% Repeat TYPO3 Customers
TYPO3 Service
wolfgang weber

Post a Comment

×