The page creation wizard introduced with forge#108915 replaced the legacy
"pages only" position selection flow of the records module. Its now unused
predecessors have been removed:
The backend route
db_new_pages
(path /record/new-page).
The page TSconfig option
mod.wizards.newRecord.pages.show.pageSelectPosition
.
Impact
Linking to the
db_new_pages
route raises an error. The page TSconfig
option
mod.wizards.newRecord.pages.show.pageSelectPosition
no longer
has any effect.
Affected installations
Instances with third-party extensions that link to the
db_new_pages
route, or set the
pageSelectPosition
page TSconfig option.
Such extensions are quite rare, and a left over page TSconfig shouldn't harm.
Migration
Use the page creation wizard instead. New pages are created through the
db_new
route and the typo3-backend-new-page-wizard-button component,
which guide position and page type selection. No replacement for
PagePositionMap
or the
pageSelectPosition
option is needed.
The following methods changed signature according to previous deprecations in v14:
\TYPO3\CMS\Core\Page\PageRenderer->render()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Page\PageRenderer->renderResponse()
- argument
$request
is now mandatory and the first argument. The transitional
ServerRequestInterface|int $requestOrCode
union has been removed (Deprecation entry)
\TYPO3\CMS\Core\Page\PageRenderer->setDocType()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Page\PageRenderer->setLanguage()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Utility\GeneralUtility::isOnCurrentHost()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Utility\GeneralUtility::sanitizeLocalUrl()
- argument
$request
is now mandatory (Deprecation entry)
\TYPO3\CMS\Extbase\Attribute\ORM\Cascade->__construct()
- argument
$value
is now a
?string
(Deprecation entry)
\TYPO3\CMS\Extbase\Attribute\IgnoreValidation->__construct()
- accepts no arguments any more (Deprecation entry)
\TYPO3\CMS\Extbase\Attribute\Validate->__construct()
- argument
$validator
is not a
string
, argument
$param
has been removed (Deprecation entry)
\TYPO3\CMS\Filelist\FileList->start()
- argument
$sortDirection
no longer accepts a
bool
, a
\TYPO3\CMS\Filelist\Type\SortDirection
enum is now required (Deprecation entry)
The following public class properties have been dropped:
\TYPO3\CMS\Backend\ElementBrowser\AbstractElementBrowser->bparams
. The legacy pipe-delimited
bparams
element browser request parameter is no longer evaluated. FormEngine now passes the individual
fieldReference
,
allowedTypes
and further parameters, which are handled by the typed
\TYPO3\CMS\Backend\ElementBrowser\ElementBrowserParameters
. Its
fromBparams()
and
toBparams()
conversion methods have been removed as well.
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['fallbackToLegacyHash']
; the transitional fallback to the legacy md5-based cHash validation has been removed, only the HMAC-SHA3 cHash is accepted (Breaking entry)
$GLOBALS['TYPO3_USER_SETTINGS']
; backend user profile settings are now configured via TCA (the
be_users
user_settings column) using
ExtensionManagementUtility::addUserSetting()
(Deprecation entry)
$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']
; additional public folders are now exposed via resource definitions instead (Deprecation entry)
The following extension file loading has been removed:
ext_tables.php files in extensions are no longer considered during bootstrap (Deprecation entry)
ext_emconf.php files in extensions are no longer evaluated for package metadata. In TYPO3 classic (non-Composer) mode the composer.json must declare the extension version and the
providesPackages
definition in the
extra/typo3/cms
section. These two fields are now mandatory, also for extensions that no longer ship an ext_emconf.php, otherwise loading the extension throws an exception. (Deprecation entry)
The following TCA options are not evaluated anymore:
passwordRules
option of the
passwordGenerator
field control; use
passwordPolicy
instead (Deprecation entry)
The following extbase attribute usages have been removed:
\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->getRequest()
no longer falls back to
$GLOBALS['TYPO3_REQUEST']
; code must call
setRequest()
after instantiation (Deprecation entry)
Page layout content area columns without an
identifier
no longer fall back to a generated hash based on the page layout identifier and
colPos
; a missing identifier now throws a
\RuntimeException
(Feature introduction)
Manually creating and adding a
\TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton
to the button bar is no longer detected and no longer suppresses the automatic shortcut button; controllers must use
\TYPO3\CMS\Backend\Template\Components\DocHeaderComponent->setShortcutContext()
instead (Deprecation entry)
A legacy typo3conf/LocalConfiguration.php and typo3conf/AdditionalConfiguration.php are no longer automatically migrated to config/system/settings.php and config/system/additional.php on first request. The configuration files have to reside at their final location. (Breaking entry)
The redis cache backend no longer accepts an array for the password option as a workaround to configure a username and password at once. Use the separate username and password options instead. (Deprecation entry)
The redis session backend no longer accepts an array for the password option as a workaround to configure a username and password at once. Use the separate username and password options instead. (Deprecation entry)
Flex form pageTsConfig (
TCEFORM
) and exclude-field addressing no longer resolves comma-separated
dataStructureKey
values (the legacy
list_type,CType
form); the data structure key is used as-is (Breaking entry)
The following JavaScript modules have been removed:
The legacy CKEditor5 alias modules
@typo3/ckeditor5-bundle.js
and
@typo3/ckeditor5-inspector.js
have been removed. Use the
@ckeditor/ckeditor5-*
modules directly. The inspector is available as
@ckeditor/ckeditor5-inspector
. (Deprecation entry)
The following JavaScript method behaviours have changed:
@typo3/backend/form-engine
no longer adds the
doSave
hidden field to the form on save actions. Third-party code must no longer rely on the
doSave
POST parameter. (Deprecation entry)
@typo3/backend/tab
no longer dispatches the legacy
show.bs.tab
and
shown.bs.tab
events on tab switches. Listen for
typo3:tab:show
(
TabShowEvent
) and
typo3:tab:shown
(
TabShownEvent
) instead. (Deprecation entry)
The following JavaScript methods have been removed:
markFieldAsChanged()
of
@typo3/backend/form-engine-validation
. Call
markFieldAsChanged()
of
@typo3/backend/form-engine
instead. (Deprecation entry)
The following localization XLIFF files/labels have been removed:
Several deprecated files (see commit)
have been removed and are too many to list. These can be identified in TYPO3 v14 source
files by searching for the XML attribute x-unused-since. (Deprecation entry)
.table-sorting-button
and
.table-sorting-icon
. These backend table sorting helper classes were not used by the core.
The following Fluid rendering mechanisms have been removed:
HeaderAssets
and
FooterAssets
Fluid template sections are no longer auto-rendered (Deprecation entry)
The following asset ViewHelper arguments and options have been removed:
The
useNonce
argument of the
f:asset.script
and
f:asset.css
ViewHelpers has been removed. Use the
csp
argument instead. (Deprecation entry)
The
useNonce
option key for JavaScript and stylesheet assets added via
\TYPO3\CMS\Core\Page\AssetCollector
has been removed. Use the
csp
option instead. (Deprecation entry)
The following FormEngine result array keys have been removed:
additionalHiddenFields
, hidden fields are now added to the
html
key directly (Deprecation entry)
The following cache action array keys have been removed:
href
in cache actions registered via
\TYPO3\CMS\Backend\Backend\Event\ModifyClearCacheActionsEvent
; use
endpoint
instead (Deprecation entry)
The following features are now always enabled:
extbase.consistentDateTimeHandling
- Extbase DateTime persistence is aligned with FormEngine and DataHandler, the feature flag has been dropped (Feature introduction)
Impact
Using above removed functionality will most likely raise PHP fatal level errors,
may change website output or crashes browser JavaScript.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterFormStateInitialized']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\AfterFormStateInitializedEvent
.
In addition, the interface
\TYPO3\CMS\Form\Domain\Runtime\FormRuntime\Lifecycle\AfterFormStateInitializedInterface
has been removed as it was only used by the hook.
Impact
Hook implementations registered under
afterFormStateInitialized
are
no longer executed in TYPO3 v15.0 and later.
Classes implementing
\TYPO3\CMS\Form\Domain\Runtime\FormRuntime\Lifecycle\AfterFormStateInitializedInterface
will cause a PHP fatal error.
Affected installations
TYPO3 installations with custom extensions using this hook or implementing
AfterFormStateInitializedInterface
are affected.
The extension scanner reports any usage as a strong match.
Migration
Register a PSR-14 event listener for
\TYPO3\CMS\Form\Event\AfterFormStateInitializedEvent
instead:
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['buildFormDefinitionValidationConfiguration']
has been removed in favor of the new PSR-14 event
\TYPO3\CMS\Form\Event\AfterFormDefinitionValidationConfigurationIsBuiltEvent
.
Impact
Any hook implementation registered under this identifier will no longer be
executed.
Affected installations
TYPO3 installations with custom extensions that register a hook class under
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['buildFormDefinitionValidationConfiguration']
are affected.
Migration
The hook has been removed without a deprecation phase to allow extensions to
remain compatible with both TYPO3 v14 (using the hook) and v15+ (using the
new event). Implementing the PSR-14 event provides the same or greater control.
Extbase resolved the target type of a controller action argument (and other
reflected method parameters) from the method DocBlock when no native PHP type
declaration was given:
This fallback was deprecated with #94115 in TYPO3 v11
in favor of native PHP type declarations. It has now been removed.
As part of this removal, the internal helper class
\TYPO3\CMS\Extbase\Reflection\DocBlock\Tags\Null_
, which only existed to
silence DocBlock parsing of this argument resolution, has been removed.
Impact
The type of a method parameter is now solely determined from its native PHP type
declaration. A parameter that relies on a
@param
DocBlock tag without a
native type declaration no longer receives a type.
For controller actions this means an
\TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
is thrown
when the action is dispatched. In combination with a
#[Validate]
attribute, an
\TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException
is thrown
while the class schema is built.
Affected installations
Extbase extensions with controller actions or other reflected methods that still
declare argument types via
@param
DocBlock tags instead of native PHP type
declarations. Such code has emitted a deprecation since TYPO3 v11 and, for the
#[Validate]
case, already raised a runtime exception since v12.
Migration
Use native PHP type declarations, available since TYPO3 v10:
The jQuery JavaScript library is no longer shipped with TYPO3. All remaining
usages in the TYPO3 backend have been migrated to native DOM APIs.
In detail, the following pieces have been removed:
The bundled library file
EXT:core/Resources/Public/JavaScript/Contrib/jquery.js
The import map entries
jquery
and
jquery/
provided by EXT:core
The JavaScript module
@typo3/backend/multi-step-wizard.js
together
with its global
TYPO3.MultiStepWizard
object
Note that
@typo3/backend/multi-step-wizard.js
has been removed without
a prior deprecation phase. It was the recommended migration target when
@typo3/backend/wizard.js
was deprecated with TYPO3 v13.1
(Deprecation entry). The module is
unused within TYPO3 itself since v14.2, when the form and translation wizards
switched to the internal
typo3-backend-wizard
web component.
Furthermore, a couple of JavaScript API signatures changed because they
returned or received jQuery objects before:
FormEngine.getFieldElement()
now returns a native
HTMLElement
or
null
instead of a jQuery collection
The callbacks of the preview, new, duplicate and delete doc header actions
of
@typo3/backend/form-engine.js
(for example
FormEngine.showPreviewModal()
) now receive a native
HTMLInputElement
or
HTMLAnchorElement
instead of a jQuery
object
The
@typo3/backend/modal.js
API no longer accepts jQuery objects as
modal content
Impact
Extensions that import
jquery
in their backend JavaScript modules will
fail to load those modules in the browser because the bare module specifier
can no longer be resolved.
Extensions using
@typo3/backend/multi-step-wizard.js
or the
TYPO3.MultiStepWizard
global will fail accordingly.
Extensions calling the changed
FormEngine
and
Modal
methods with
jQuery semantics will trigger JavaScript errors.
Affected installations
All installations with extensions that load jQuery via the TYPO3 core import
map, use the multi step wizard module, or rely on the jQuery based
FormEngine
and
Modal
API signatures in their backend JavaScript.
Migration
jQuery
Extensions that still depend on jQuery need to ship their own copy of the
library and register it in their import map configuration:
Consider migrating extension JavaScript to native DOM APIs instead.
Multi step wizard
There is no direct replacement for the removed
@typo3/backend/multi-step-wizard.js
module. TYPO3 itself uses the
typo3-backend-wizard
web component, which is currently internal and
provides no API stability guarantees yet. Extensions should either bring
their own wizard implementation, or copy the removed module into their own
codebase together with a bundled jQuery.
FormEngine
Adapt callers of the changed
FormEngine
methods to the native return
types:
-const field = FormEngine.getFieldElement(fieldName).get(0);+const field = FormEngine.getFieldElement(fieldName);
The following PHP methods now use strict type declarations
instead of loose type hints (PHPdoc annotations).
This is considered a breaking change in case consumers are
not adjusted for strict types.
\TYPO3\CMS\Backend\Utility::daysUntil
- Parameter $tstamp can now only be of type integer or DateTimeInterface - cast to (int) if strings were passed to this argument before.
\TYPO3\CMS\Backend\Form\FormDataGroupInterface::compile
- Return type now specifies array, implementations of this interface need to declare this as well.
Impact
Using the mentioned methods with wrong types will now result in a PHP exception, fatal error or warning,
depending on configured error reporting.
Migration
Ensure proper PHP type declarations are used when calling these methods.
\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer
has been turned
into a stateless, dependency-injected service. It is now declared
final
and can no longer be subclassed. The lazy initialize() workaround that
resolved its dependencies through
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance()
on first use has
been removed, and all dependencies are passed through the constructor instead.
Impact
Custom preview renderers extending
\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer
raise a fatal
error, as the class is now
final
.
Affected installations
Instances with third-party extensions that subclass
StandardContentPreviewRenderer
or instantiate it manually.
Migration
Instead of subclassing
StandardContentPreviewRenderer
, implement
\TYPO3\CMS\Backend\Preview\PreviewRendererInterface
directly. The standard
renderer may be composed and its rendering methods delegated to where its output
is desired, as
\TYPO3\CMS\Form\Preview\FormPagePreviewRenderer
demonstrates.
Breaking: #110028 - LoggerAwareInterface removed from FormEngine base classes
The FormEngine base classes
\TYPO3\CMS\Backend\Form\AbstractNode
and
\TYPO3\CMS\Backend\Form\FormDataProvider\AbstractDatabaseRecordProvider
no longer implement
\Psr\Log\LoggerAwareInterface
and no longer use
\Psr\Log\LoggerAwareTrait
.
Both base classes never read the injected logger themselves. The node hierarchy
built on
AbstractNode
does not use a logger at all. The two data provider
subclasses that did log,
\TYPO3\CMS\Backend\Form\FormDataProvider\TcaFiles
and
\TYPO3\CMS\Backend\Form\FormDataProvider\TcaInline
, now receive a
\Psr\Log\LoggerInterface
through their constructor instead.
Impact
Subclasses of these base classes, for example custom FormEngine nodes or custom
database record providers shipped by extensions, are no longer recognized as
\Psr\Log\LoggerAwareInterface
instances and no longer have a logger
injected automatically. The inherited
setLogger()
method is gone, and
reading the previously inherited
$logger
property raises an error.
Affected installations
Instances with third-party extensions that subclass
\TYPO3\CMS\Backend\Form\AbstractNode
or
\TYPO3\CMS\Backend\Form\FormDataProvider\AbstractDatabaseRecordProvider
and rely on the automatically injected logger.
Migration
Request a
\Psr\Log\LoggerInterface
as a constructor argument in the
affected subclass. The subclass must be registered as a public service using the
#[Autoconfigure(public: true)]
attribute, so that the FormEngine
\TYPO3\CMS\Backend\Form\NodeFactory
obtains it through the dependency
injection container, which then resolves the constructor argument automatically.
usePsr\Log\LoggerInterface;
useSymfony\Component\DependencyInjection\Attribute\Autoconfigure;
useTYPO3\CMS\Backend\Form\AbstractNode;
#[Autoconfigure(public: true)]finalclassMyFormElementextendsAbstractNode{
publicfunction__construct(
private readonly LoggerInterface $logger,
){}
publicfunctionrender(): array{
$this->logger->warning('Something the element wants to log');
// ...
}
}
Copied!
Breaking: #110188 - DataHandler: Drop property bypassAccessCheckForRecords
The property
DataHandler->bypassAccessCheckForRecords
has been removed.
Impact
Using
DataHandler->bypassAccessCheckForRecords
will raise a PHP warning or a fatal error.
There will be no effect on access checks anymore.
Affected installations
All installations using
DataHandler->bypassAccessCheckForRecords
.
This could be in DataHandler hooks or in custom DataHandler calls.
Migration
Use a proper BackendUserAuthentication with sufficient access rights in
DataHandler->start()
instead for custom DataHandler calls.
Check for
DataHandler->BE_USER
rights in DataHandler hooks.
Breaking: #110196 - PHP class Rfc822AddressesParser removed
The PHP class
\TYPO3\CMS\Core\Mail\Rfc822AddressesParser
has been
removed. The class was a modernized copy of the PEAR package
pear/mail
, dating back to 2010, and was only used internally by
\TYPO3\CMS\Core\Utility\MailUtility::parseAddresses()
.
This internal usage is now based on
\Symfony\Component\Mime\Address
of the
symfony/mime
package, which TYPO3 already utilizes for
sending emails.
Impact
Instantiating or referencing the class
Rfc822AddressesParser
will raise
a fatal PHP error.
In addition,
MailUtility::parseAddresses()
now strips surrounding
double quotes from display names: parsing
'"last, first" <email@example.org>'
previously returned the display
name
'"last, first"'
and now returns
'last, first'
. Quoting is
re-applied automatically by
symfony/mime
when composing a mail
message.
Affected installations
TYPO3 installations with third-party extensions directly using the class
Rfc822AddressesParser
. The extension scanner reports any usage as a
strong match.
Migration
To parse a comma-separated list of email addresses with optional display
names, use
\TYPO3\CMS\Core\Utility\MailUtility::parseAddresses()
,
which continues to work as before.
Alternatively, use the
symfony/mime
API directly to parse a
single mailbox string:
The minimum PHP version required to run TYPO3 has been raised to PHP 8.5.
Impact
The TYPO3 Core codebase and extensions tailored for this version can use
features implemented with PHP up to and including 8.5. Running TYPO3 with
older PHP versions will trigger fatal errors.
Affected installations
All installations running a PHP version lower than 8.5.
Migration
Update the PHP platform to PHP 8.5 before upgrading to this TYPO3 version.
Previous TYPO3 versions already support PHP 8.5, which allows upgrading
the PHP platform in a first step and TYPO3 in a second step.
Breaking: #110218 - Class LogRecord declared final
The PHP class
\TYPO3\CMS\Core\Log\LogRecord
— the value object passed
to log writers and processors — has been declared
final
and is now
instantiated directly via
new
within
\TYPO3\CMS\Core\Log\Logger
, instead of using
GeneralUtility::makeInstance()
.
A log record is a plain data transfer object created for every single log
entry. Routing its creation through
makeInstance()
allowed the class
to be overridden via XCLASS, but caused an unnecessary container lookup in
one of the most frequently executed code paths of the logging API.
In addition, the class now declares
strict_types
and uses native
type declarations for all method signatures instead of loose PHPDoc
annotations.
Impact
Extending or XCLASSing
LogRecord
is not possible anymore. Extension
classes will raise a fatal PHP error, XCLASS configurations for this class
are silently ignored.
Calling a method of
LogRecord
with a wrong argument type — for
example a non-float value for
setCreated()
— will raise a PHP
\TypeError
, depending on the
strict_types
mode of the
calling code.
Creating, reading and modifying log records — for example in custom
implementations of
\TYPO3\CMS\Core\Log\Processor\ProcessorInterface
or
\TYPO3\CMS\Core\Log\Writer\WriterInterface
— continues to work as
before.
Affected installations
TYPO3 installations with third-party extensions that extend or XCLASS the
class
LogRecord
, which is very unlikely.
Migration
To enrich or modify log records, implement a custom log processor
(
ProcessorInterface
), which may return a modified or newly created
LogRecord
instance. Custom output handling belongs into a log writer
(
WriterInterface
).
Breaking: #110219 - Log request ID provided by log processor
The unique ID of the current request, used to correlate all log entries
written during a single request, was previously passed as constructor
argument from
\TYPO3\CMS\Core\Log\LogManager
to every
\TYPO3\CMS\Core\Log\Logger
instance, which in turn copied it into
each created
\TYPO3\CMS\Core\Log\LogRecord
.
The request ID is now added to log records at logging time by the new log
processor
\TYPO3\CMS\Core\Log\Processor\RequestIdProcessor
, which
LogManager
attaches automatically to every logger it creates, for
all severity levels covered by the configured log writers. The processor
is registered internally on purpose — not via
$GLOBALS['TYPO3_CONF_VARS']['LOG']
— so it cannot be removed
accidentally by overriding the global processor configuration.
The following method signatures have changed:
Logger::__construct()
no longer accepts a second
$requestId
argument, the protected property
Logger::$requestId
has been removed
LogManager::__construct()
now expects a
\TYPO3\CMS\Core\Core\RequestId
object instead of a string, and
creates one itself if omitted
The protected method
LogManager::makeLogger()
no longer receives
a
$requestId
argument
The generated log output is unchanged: log records written by configured
writers carry the same request ID as before, available via
LogRecord::getRequestId()
.
Impact
Instantiating
LogManager
with a string request ID will raise a PHP
\TypeError
.
Passing a second argument to the
Logger
constructor is ignored.
Log records created by a manually instantiated
Logger
— bypassing
LogManager
— no longer contain a request ID, unless the
RequestIdProcessor
is attached manually.
Affected installations
TYPO3 installations with third-party extensions instantiating
Logger
or
LogManager
directly with a custom request ID,
which is very unlikely. Extensions obtaining loggers via dependency
injection, the
#[Channel]
attribute,
LoggerAwareInterface
or
LogManager->getLogger()
are not affected.
Migration
Obtain loggers through dependency injection or
LogManager->getLogger()
, which attach the
RequestIdProcessor
automatically.
For manually created loggers that should add the request ID to their
log records, attach the processor explicitly:
useTYPO3\CMS\Core\Core\RequestId;
useTYPO3\CMS\Core\Log\Logger;
useTYPO3\CMS\Core\Log\Processor\RequestIdProcessor;
$logger = new Logger('my.channel');
$logger->addWriter(LogLevel::WARNING, $myWriter);
$logger->addProcessor(LogLevel::WARNING, new RequestIdProcessor(new RequestId()));
The PHP class
\TYPO3\CMS\Core\Log\Logger
has been declared
final
and is now instantiated directly via
new
within
\TYPO3\CMS\Core\Log\LogManager
, instead of using
GeneralUtility::makeInstance()
.
The logger is a plain, per-channel object created and configured by the
LogManager
. Routing its creation through
makeInstance()
only
served to allow overriding the class via XCLASS, which is not a supported
extension point for the logging API.
Impact
Extending or XCLASSing
Logger
is not possible anymore. Extension
classes will raise a fatal PHP error, XCLASS configurations for this class
are silently ignored.
Obtaining and using loggers via dependency injection, the
#[Channel]
attribute,
LoggerAwareInterface
or
LogManager->getLogger()
continues to work as before.
Affected installations
TYPO3 installations with third-party extensions that extend or XCLASS the
class
Logger
, which is very unlikely.
Migration
To customize logging behavior, implement the PSR-3
\Psr\Log\LoggerInterface
and provide the instances through a custom
\TYPO3\CMS\Core\Log\LogManagerInterface
implementation, or attach
custom log writers (
\TYPO3\CMS\Core\Log\Writer\WriterInterface
) and
processors (
\TYPO3\CMS\Core\Log\Processor\ProcessorInterface
) to the
existing logger, which is the designated way to influence how log records
are handled.
Breaking: #110250 - Remove implicit output buffering from Bootstrap
The class
\TYPO3\CMS\Core\Core\Bootstrap
no longer wraps the entire
bootstrap and request lifecycle in an implicit output buffer via
ob_start()
. This buffer was historically opened as the very first
action in
Bootstrap::init()
to swallow accidental stray output
(warnings, notices or stray
echo
/
print
statements, typically
from
ext_localconf.php
/
ext_tables.php
of third-party
extensions) so it would not corrupt AJAX responses or file downloads. On
CLI this buffering was already disabled again in a previous change, as it
delayed console output until the process ended and could bloat memory for
commands producing a lot of output.
Since responses are fully assembled as PSR-7 message bodies before being
emitted, this legacy safety net is not needed anymore.
Along with it, the now unused method
Bootstrap::startOutputBuffering()
has been removed.
Impact
Any stray output produced during bootstrap (for example from
ext_localconf.php
) is now sent to the client directly instead of
being silently discarded later on. This mainly affects binary/file
responses and AJAX requests, which relied on the buffer being cleaned
right before the response body was emitted.
Calling
Bootstrap::startOutputBuffering()
now results in a fatal
PHP error (
Call to undefined method
).
Affected installations
TYPO3 installations with extensions that produce accidental output during
ext_localconf.php
/
ext_tables.php
loading, which so far was
silently swallowed, and installations with custom entry point scripts
calling
Bootstrap::startOutputBuffering()
directly.
Migration
Remove any accidental output from
ext_localconf.php
and
ext_tables.php
files. Extensions that need to suppress their own
output must manage output buffering (
ob_start()
/
ob_end_clean()
) themselves.
Remove calls to
Bootstrap::startOutputBuffering()
and use
ob_start()
directly, if the behaviour is still required.
Breaking: #110277 - File renderer registration and interface changed
File renderers, used for example by the
<f:media>
ViewHelper to
render audio, video or online media files, are now registered as tagged
services via dependency injection (see
Feature: #110277 - Register file renderers as tagged services). This comes with the following breaking
changes:
\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry->registerRendererClass()
is now a no-op. Calling the method has no effect anymore, but triggers
an
E_USER_DEPRECATED
notice. The method will be removed in
TYPO3 v16.0.
\TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface
does
not extend
\TYPO3\CMS\Core\SingletonInterface
anymore. File
renderers are shared services managed by the dependency injection
container.
The method
getPriority()
has been removed from
FileRendererInterface
. The renderer priority is now defined at
registration time via the
#[AsFileRenderer]
attribute or the
fal.file_renderer
service tag. Note that the file renderers
shipped with TYPO3 Core previously returned a priority of
1
from
getPriority()
and are now registered with the attribute's
default priority of
0
. Custom renderers that relied on a
priority of
0
to rank strictly below all Core renderers now
rank equally with them instead and should use a negative priority to
keep the previous ordering.
Renderers registered with the same priority are no longer guaranteed
to be asked in the order they were added: previously, same-priority
renderers kept the order in which
RendererRegistry->registerRendererClass()
was called from
ext_localconf.php. The order of same-priority tagged services
is now an implementation detail of the dependency injection container
and must not be relied upon. Extensions that depend on a specific
evaluation order between renderers should assign distinct priorities
instead.
The remaining methods of
FileRendererInterface
are now
strictly typed:
canRender(FileInterface $file): bool
and
render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
.
The methods
createRendererInstance()
and
compareRendererPriority()
have been removed from
RendererRegistry
, the method
getRendererInstances()
has
been changed from public to protected visibility, and the remaining
methods are now strictly typed.
RendererRegistry
is now marked as
@internal
, since
registering file renderers does not require interacting with the
registry anymore. TYPO3 Core resolves the matching renderer via
getRenderer()
internally, for example in the
<f:media>
ViewHelper.
Impact
File renderers registered via
RendererRegistry->registerRendererClass()
in
ext_localconf.php are no longer evaluated. The corresponding
files (audio, video or online media) are no longer rendered by the custom
renderer until it is registered as a tagged service.
Custom renderer classes implementing
FileRendererInterface
without
the adapted method signatures will cause a fatal PHP error.
Affected installations
All installations with custom extensions registering file renderers via
RendererRegistry->registerRendererClass()
, or providing custom
implementations of
FileRendererInterface
. The extension scanner
reports usages of
registerRendererClass()
as weak match.
Migration
Remove the
RendererRegistry->registerRendererClass()
call from
ext_localconf.php and add the
#[AsFileRenderer]
attribute
to the renderer class instead. Move the priority previously returned by
getPriority()
to the attribute and remove the method. Add the
native type declarations to
canRender()
and
render()
:
In case the extension supports both TYPO3 v14 and v15, keep the
getPriority()
method (it is simply unused in v15) and register the
renderer in both ways: the ext_localconf.php registration is
evaluated in v14, the attribute in v15. Since PHP parameter types must
not be narrowed in implementations, keep the
$width
and
$height
parameters untyped in this case — only the
bool
and
string
return type declarations are compatible with both
versions.
Code that called
RendererRegistry->getRendererInstances()
to
inspect all registered renderers should inject
RendererRegistry
and use
getRenderer($file)
to retrieve the matching renderer for
a given file instead.
Breaking: #110286 - Text extractor registration and interface changed
\TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorRegistry->registerTextExtractor()
is now a no-op. Calling the method has no effect anymore. The method
will be removed in TYPO3 v16.0.
Extractors registered with the same priority are no longer
guaranteed to be asked in the order they were added: previously,
extractors kept the order in which
TextExtractorRegistry->registerTextExtractor()
was called from
ext_localconf.php. The order of same-priority tagged services
is now an implementation detail of the dependency injection container
and must not be relied upon. Extensions that depend on a specific
evaluation order between extractors should assign distinct priorities
instead.
The methods of
\TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorInterface
are now strictly typed:
canExtractText(FileInterface $file): bool
and
extractText(FileInterface $file): string
.
The method
createTextExtractorInstance()
has been removed
from
TextExtractorRegistry
, the method
getTextExtractorInstances()
has been changed from public to
protected visibility, and the remaining methods are now strictly
typed. The public API of the registry is
getTextExtractor()
, which returns the first matching
extractor for a given file.
Impact
Text extractors registered via
TextExtractorRegistry->registerTextExtractor()
in
ext_localconf.php are no longer evaluated: no text is extracted
by the custom extractor until it is registered as a tagged service.
Custom extractor classes implementing
TextExtractorInterface
without the adapted method signatures will cause a fatal PHP error.
Affected installations
All installations with custom extensions registering text extractors
via
TextExtractorRegistry->registerTextExtractor()
, or providing
custom implementations of
TextExtractorInterface
. The extension
scanner reports usages of
registerTextExtractor()
as weak match.
Migration
Remove the
TextExtractorRegistry->registerTextExtractor()
call
from ext_localconf.php and add the
#[AsTextExtractor]
attribute to the extractor class instead. Add the native type
declarations to
canExtractText()
and
extractText()
:
In case the extension supports both TYPO3 v14 and v15, register the
extractor in both ways: the ext_localconf.php registration is
evaluated in v14, the attribute in v15. The
bool
and
string
return type declarations are compatible with both
versions.
Code that called
TextExtractorRegistry->getTextExtractorInstances()
to inspect all registered extractors should inject
TextExtractorRegistry
and use
getTextExtractor($file)
to
retrieve the matching extractor for a given file instead.
Breaking: #110287 - Meta tag manager registration and interface changed
\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry->registerManager()
is now a no-op. Calling the method has no effect anymore. The method
will be removed in TYPO3 v16.0.
The method
removeAllManagers()
has been removed from
MetaTagManagerRegistry
, since the list of managers is
compiled into the dependency injection container and cannot be
changed at runtime anymore.
The methods
addProperty()
,
removeProperty()
and
removeAllProperties()
of
\TYPO3\CMS\Core\MetaTag\MetaTagManagerInterface
now declare
a native
void
return type.
Impact
Meta tag managers registered via
MetaTagManagerRegistry->registerManager()
in
ext_localconf.php are no longer evaluated: their meta tag
properties are handled by the generic meta tag manager until the manager
is registered as a tagged service.
Custom classes implementing
MetaTagManagerInterface
directly
without the adapted method signatures will cause a fatal PHP error.
Classes extending
\TYPO3\CMS\Core\MetaTag\AbstractMetaTagManager
are only affected if they override one of the changed methods.
Affected installations
All installations with custom extensions registering meta tag managers
via
MetaTagManagerRegistry->registerManager()
, or providing custom
implementations of
MetaTagManagerInterface
. The extension scanner
reports usages of the changed methods as weak match.
Migration
Remove the
MetaTagManagerRegistry->registerManager()
call from
ext_localconf.php and add the
#[AsMetaTagManager]
attribute to the manager class instead:
Move the manager name previously passed as first argument to
registerManager()
to the
identifier
argument of the
attribute, and any
before
/
after
arguments to the
corresponding attribute arguments.
In case the extension supports both TYPO3 v14 and v15, register the
manager in both ways: the ext_localconf.php registration is
evaluated in v14, the attribute in v15. The
void
return type
declarations are compatible with both versions.
Breaking: #110319 - Encryption key is checked before ext_localconf.php loading
\TYPO3\CMS\Core\Core\Bootstrap
verifies that
$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
is not empty and
throws a
\RuntimeException
otherwise. This check has been moved to
run before the
ext_localconf.php
files of all active extensions are
loaded. Previously it ran afterwards, and additionally after
$GLOBALS['TCA']
had been populated.
The encryption key is part of the system configuration and is used for
security-relevant functionality. It must therefore be in place before any
extension code is executed during bootstrap.
In addition,
$GLOBALS['TCA']
is now assigned after
TcaSchemaFactory->load()
has been called, so consumers can not
observe a state where the global array is populated but the schema
information is not yet available.
Impact
Installations that set
$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
from within an
ext_localconf.php
file now run into the
"TYPO3 Encryption Key is empty" exception, as the check is executed before
the file is evaluated.
Code within
ext_localconf.php
files can not rely on
$GLOBALS['TCA']
being available anymore - which was never a
guaranteed state, as
ext_localconf.php
files are loaded before TCA
is built. This is unchanged, but the assignment now happens later within
the bootstrap.
Affected installations
Installations that populate the encryption key at runtime from an
ext_localconf.php
file, for instance by reading it from an
environment variable or an external secret store.
Migration
Set the encryption key in the system configuration, either directly in
config/system/settings.php or in
config/system/additional.php, both of which are evaluated before
the check:
The following methods of
\TYPO3\CMS\Core\Core\Bootstrap
have been
removed:
Bootstrap::baseSetup()
Bootstrap::createConfigurationManager()
Bootstrap::populateLocalConfiguration()
All three were marked
@internal
and are not used by TYPO3 Core
anymore.
Bootstrap::baseSetup()
only registered the class loading
information in non-Composer mode, which
Bootstrap::init()
does
itself. The two configuration related methods have been inlined into
Bootstrap::init()
, as they consisted of a single statement each.
Impact
Calling one of the removed methods results in a fatal PHP error
(
Call to undefined method
).
Affected installations
TYPO3 installations with third-party extensions or custom entry point
scripts that call
Bootstrap::baseSetup()
or
Bootstrap::createConfigurationManager()
directly - a pattern
sometimes used in custom, non-Composer bootstrapping scripts.
Bootstrap::populateLocalConfiguration()
was already
protected
and could not be called from the outside.
The extension scanner reports possible usages.
Migration
Remove calls to
Bootstrap::baseSetup()
,
Bootstrap::init()
performs the equivalent setup internally.
Replace
Bootstrap::createConfigurationManager()
with a direct
instantiation:
$configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager();
Copied!
Breaking: #110334 - XML sitemap data provider interface changed
\TYPO3\CMS\Seo\XmlSitemap\XmlSitemapDataProviderInterface
declared a
constructor, which forced every data provider to be instantiated with its
runtime information as constructor arguments. Data providers could therefore
never be resolved by the dependency injection container and had to fetch all
their dependencies statically. In addition, the items of a sitemap had to be
collected in the constructor, since the interface methods took no arguments.
The interface has been changed accordingly:
The constructor declaration and the methods
getKey()
,
getItems()
,
getLastModified()
and
getNumberOfPages()
have been removed in favor of the single method
getSitemap()
.
getSitemap()
receives all runtime information as
\TYPO3\CMS\Seo\XmlSitemap\XmlSitemapRequest
: The name of the
requested sitemap, its configuration, the number of the requested page, the
current request and a content object renderer to generate URLs with.
getSitemap()
returns a
\TYPO3\CMS\Seo\XmlSitemap\XmlSitemap
carrying the items of the requested page, the last modification date and
the number of pages of the sitemap.
Data providers are stateless services now. The interface is tagged with
seo.xmlsitemap.provider
, so implementations are registered in the
dependency injection container automatically and are free to use
constructor injection.
Registering a sitemap in TypoScript is unchanged: Data providers are still
referenced by their class name.
Impact
Classes implementing
XmlSitemapDataProviderInterface
without the method
getSitemap()
will cause a fatal PHP error.
Data providers that are not available as a service in the dependency injection
container are not resolved anymore: Rendering such a sitemap throws a
\TYPO3\CMS\Seo\XmlSitemap\Exception\InvalidConfigurationException
, and
the sitemap is skipped in the sitemap index.
All installations with custom extensions implementing
XmlSitemapDataProviderInterface
directly. This is a rarely used API,
most data providers extend
AbstractXmlSitemapDataProvider
instead.
Migration
Move the collecting of items from the constructor to
getSitemap()
and
take the configuration, the requested page and the content object renderer
from the
XmlSitemapRequest
. The constructor is free for dependency
injection afterwards:
XmlSitemap::forPage()
takes all items of a sitemap, extracts the items of
the requested page and calculates the last modification date and the number of
pages needed by the sitemap index. The optional item mapper is applied to the
items of the requested page only, and only when the items are rendered at all -
the sitemap index does not generate any URL this way. Data providers taking care
of paging themselves, for example by limiting their database query to the items
of the requested page, use
XmlSitemap::create()
instead:
In case the extension supports both TYPO3 v14 and v15, extend
AbstractXmlSitemapDataProvider
instead of implementing the interface
directly: Such a data provider is instantiated with its runtime information in
v15 as well, at the cost of a deprecation message. It is then migrated to the
new interface once support for TYPO3 v14 is dropped.
Breaking: #110414 - Remove global access time variables
The global variables
$GLOBALS['ACCESS_TIME']
and
$GLOBALS['SIM_ACCESS_TIME']
have been removed.
Both were introduced in 2008 and held the current (respectively the simulated)
execution time floored to the full minute. Their only purpose was to keep the
SQL of
starttime
/
endtime
comparisons textually identical for up
to 60 seconds, so that the MySQL query cache could serve repeated requests. That
query cache has been removed from MySQL and is disabled by default in MariaDB,
so the original motivation no longer applies.
Beyond that, both values were pure derivatives of
$GLOBALS['EXEC_TIME']
and
$GLOBALS['SIM_EXEC_TIME']
that had to be kept in sync manually, which
was an error-prone contract.
The minute granularity itself is kept, since record visibility and the frontend
cache lifetime derived from
starttime
/
endtime
must use the very
same clock. It now lives in the date aspect of the Context API, which gained two
typed methods:
The already existing property
$context->getPropertyFromAspect('date', 'accessTime')
is unchanged and now delegates to the latter method.
As a consequence,
\TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
and
\TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
fall back
to the date aspect instead of the global variable when no timestamp is handed in.
The
\RuntimeException
with codes
1462820645
and
1462821084
,
which was thrown when the global was missing, has become unreachable and has been
removed.
Impact
Reading
$GLOBALS['ACCESS_TIME']
or
$GLOBALS['SIM_ACCESS_TIME']
raises an "Undefined array key" warning and evaluates to
null
.
Writing to either variable no longer has any effect. In particular, simulating
another point in time by setting
$GLOBALS['SIM_ACCESS_TIME']
is silently
ignored, and records are then evaluated against the real current time.
The following constructors received an additional
\TYPO3\CMS\Core\Context\Context
argument:
Installations with extensions that read or write one of the two global
variables, that rely on the removed exceptions of the start and end time
restrictions, or that instantiate one of the above classes manually instead of
using dependency injection.
Migration
Use the date aspect of the Context API instead of the global variables:
useTYPO3\CMS\Core\Context\Context;
// Before
$accessTime = $GLOBALS['SIM_ACCESS_TIME'];
// After, with dependency injectionpublicfunction__construct(private readonly Context $context){}
$accessTime = $this->context->getAspect('date')->getTimestampWithMinutePrecision();
Copied!
In places without dependency injection, the current context is fetched via
GeneralUtility::makeInstance(Context::class)
.
To simulate another point in time, set the date aspect instead of writing to
$GLOBALS['SIM_ACCESS_TIME']
:
useTYPO3\CMS\Core\Context\DateTimeAspect;
useTYPO3\CMS\Core\Domain\DateTimeFactory;
$context->setAspect('date', new DateTimeAspect(DateTimeFactory::createFromTimestamp($timestamp)));
Copied!
Breaking: #110485 - Menu content object registration changed
Menu content objects - the sub types of the
HMENU
content
object, for example
TMENU
- are now registered as tagged
services via dependency injection. This comes with the following breaking
changes:
The class
\TYPO3\CMS\Frontend\ContentObject\Menu\MenuContentObjectFactory
has been removed. It was marked
@internal
, but its method
registerMenuType()
was the only way to add or override a menu
type and was therefore used from ext_localconf.php.
The exception
\TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException
has been removed. An unregistered menu type is no longer signalled by
an exception, the menu is simply not rendered - which is the behavior
that was already visible from the outside, since all Core call sites
caught the exception and continued silently.
\TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
now declares a constructor which requires the service locator of all
registered menu content objects as first argument. Subclasses which
declare their own constructor must hand this argument to
parent::__construct()
.
Menu content objects are never shared: every
HMENU
level
is rendered by its own instance. Instantiating them through
GeneralUtility::makeInstance()
is not supported anymore, they
have to be retrieved from the service locator.
The constructor argument of
AbstractMenuContentObject
is set by the
dependency injection container for all services tagged as
frontend.menucontentobject
, together with
shared: false
.
Registering a menu type therefore only requires the tag itself.
Impact
Menu types registered via
MenuContentObjectFactory->registerMenuType()
in
ext_localconf.php cause a fatal PHP error, since the class does
not exist anymore.
Custom classes extending
AbstractMenuContentObject
with an own
constructor cause a fatal PHP error as soon as they are instantiated,
unless they pass the service locator on to
parent::__construct()
.
Affected installations
All installations with custom extensions registering an own menu type, or
overriding the
TMENU
implementation. Since the only menu type
shipped by TYPO3 Core is
TMENU
, most installations are not
affected. The extension scanner reports usages of
MenuContentObjectFactory
and
registerMenuType()
.
Migration
Remove the
registerMenuType()
call from ext_localconf.php
and register the menu content object as a tagged service instead. The
identifier
is the menu type as used in TypoScript and has to be
written in upper case:
An existing menu type - including
TMENU
- is overridden by
registering an own class with the same
identifier
. As extensions
are loaded after TYPO3 Core, the last registration for an identifier wins.
Custom classes extending
AbstractMenuContentObject
which declare
their own constructor have to accept and pass on the service locator:
Code which created a menu content object through
MenuContentObjectFactory->getMenuObjectByType()
should inject the
locator of all registered menu content objects instead:
\TYPO3\CMS\Core\SingletonInterface
predates dependency injection.
Today it has exactly one effect: it turns a class into a shared and
public service in the dependency injection container, so that the legacy
GeneralUtility::makeInstance()
can still reach it.
Both properties are already the default for injected services. Every
service in the container is shared, so every class that receives a service
through constructor injection gets the very same instance - that is what
the marker used to guarantee. A service only needs to be public on top
of that if it is pulled out of the container by hand.
The marker has therefore been removed from a number of Core services that
are resolved through dependency injection anyway. Their lifetime does not
change: they are still shared.
Impact
These classes do not implement
SingletonInterface
anymore:
GeneralUtility::setSingletonInstance()
and
GeneralUtility::removeSingletonInstance()
only accept
SingletonInterface
instances and raise a
TypeError
for these
classes. This mostly affects tests that substitute one of them.
Every type converter extending
AbstractTypeConverter
is affected as
well, since the marker was inherited. Converters registered with the
extbase.type_converter
tag stay public, because the tag makes them
public on its own.
Some of these services are not public anymore either, so fetching them
with
GeneralUtility::makeInstance()
or
$container->get()
fails with an
ArgumentCountError
or a
ServiceNotFoundException
. Whether a Core service is public is an
implementation detail and must not be relied upon.
Affected installations
Installations with extensions that fetch one of the listed classes through
GeneralUtility::makeInstance()
or the container, or that replace one
of them in tests via
GeneralUtility::setSingletonInstance()
.
Migration
Inject the service instead of fetching it. Since services are shared, the
injected instance is the same one the rest of the request uses:
EXT:my_extension/Classes/Service/MyService.php
useTYPO3\CMS\Core\Localization\Locales;
final readonly classMyService{
publicfunction__construct(
private Locales $locales,
){}
publicfunctiondoSomething(): void{
// instead of GeneralUtility::makeInstance(Locales::class)$this->locales->createLocale('de');
}
}
Copied!
The same applies to tests that replaced one of these classes globally with
GeneralUtility::setSingletonInstance()
. Hand the dependency to the
subject instead: a stub or a mock if the test only needs the collaborator
to be there, or a functional test if it should exercise the real one.
This is cleaner and framework-agnostic: the subject is built with plain
PHP, and everything it works with is visible in the test itself, instead
of being smuggled in through a global registry that the code under test
happens to read from.
SingletonInterface
itself is not deprecated and keeps working for
custom classes. If a class of your own cannot use dependency injection -
typically because it is instantiated with constructor arguments, which
bypasses the container - it can also be made public explicitly, without
the marker:
The record browser in forms can now be used to add multiple records to a property,
if configured accordingly.
When the option
maxItems
of the
Inspector-Typo3WinBrowserEditor
is
greater than 1, the UIDs of the selected records are added as a comma-separated list:
Custom logic must be added to actually make use of the multiple values.
Whenever
minItems
or
maxItems
is configured, the number of
selected records is validated automatically through the new
ItemCount
property validator, without the need to add it to
propertyValidators
.
There is also a new property validator:
IntegerList
checks whether all elements in the comma-separated list
are integers.
Impact
Form definitions can be set up to allow editors the selection of multiple database
records and then render them using custom logic.
To avoid conflicts with existing configurations,
minItems
is set to 0 and
maxItems
to 1 by default.
Feature: #94886 - Show selected item counts in FormEngine
The backend FormEngine now displays a compact badge for fields configured via
TCA
types
and
renderTypes
when they use item count validation
rules such as
minItems
and
maxItems
.
The badge reflects the currently selected number of items and the configured
minimum or maximum values, and it updates automatically when entries are
added or removed. This improves the visibility of selection limits for
supported field types including select, category, group, folder and inline
elements.
When the maximum allowed number of items is reached, the max-items notice
continues to be shown as before. If a field requires a minimum number of
selected items, the badge also indicates that the current selection has not
yet reached the required value.
Impact
Editors get immediate visual feedback about how many items are currently
selected in relation to the configured
minItems
and
maxItems
limits. The badge is injected on the client side for any field exposing item
count validation rules, so it also applies to custom render types without
requiring additional PHP code.
Feature: #107345 - Language filter for the recycler module
The recycler backend module now provides a language switcher in the module
doc header, comparable to the one in the record history module.
Selecting a language restricts the list of deleted records to that specific
language. The relevant language field is resolved per table through the TCA
schema
Language
capability, so tables that use a custom language field
name are handled correctly.
Tables that are not language aware are omitted from the result while a language
is selected. Choosing the [All] entry shows all deleted records,
independent of their language or language awareness.
The selected language is persisted as module data for the backend user, so the
chosen language context is remembered across requests, just like the depth and
table selection. The language dropdown is only shown when there are
at least two languages present, otherwise it defaults to showing
all records.
Additionally, the language icon and name is now shown inside the recycler
record listing for each record that is language aware and has a language
set (also -1 is evaluated as all languages).
Impact
Editors can now narrow down deleted records to a single language in the
recycler module, making it easier to locate and restore translated content.
Feature: #109444 - Add default value support for CountrySelect form element
The TYPO3 Form Framework's
CountrySelect
element now supports a
defaultValue
property. Previously only
prioritizedCountries
,
onlyCountries
, and
excludeCountries
could be configured — there
was no way to pre-select a country when the form is initially rendered.
The default value can be set in two ways:
Directly in the YAML form definition.
Through the new
Inspector-CountrySingleSelectEditor
in the Form
Editor backend module.
The new inspector editor is a single-select country dropdown that appears in
the Form Editor's inspector panel. It lists all available countries regardless
of any configured
onlyCountries
,
excludeCountries
, or
prioritizedCountries
filters. A description hint reminds editors to
ensure the selected country is not excluded by the configured country filters.
If the configured
defaultValue
refers to a country that is excluded by
a country filter, the default value has no visible effect on the frontend
because the corresponding
<option>
element will not be rendered.
On the frontend the
defaultValue
is resolved through the existing form
framework property binding, so no additional rendering logic is required.
Impact
Integrators and editors can now pre-select a country for
CountrySelect
form elements. The default value can be configured via YAML or through the
Form Editor backend module without writing custom code.
A new PSR-14 event
\TYPO3\CMS\Form\Event\AfterFormStateInitializedEvent
has been introduced. It serves as an improved replacement for the now
removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterFormStateInitialized']
.
The new event is dispatched by
FormRuntime
after the
FormState
has been restored from the request. At this point both the form state
(submitted values) and the static form definition are available, which makes
it particularly suitable for enriching components that need runtime data (e.g.
configuring property mapping for file uploads).
The event provides the following public properties:
$formRuntime
: The form runtime object (read-only).
With the new
AfterFormStateInitializedEvent
,
it is now possible to react to the form state being fully initialized using the
standard PSR-14 event listener mechanism.
Feature: #109836 - PSR-14 event before form value is rendered
A new PSR-14 event
\TYPO3\CMS\Form\Event\BeforeFormValueIsRenderedEvent
has been introduced. It is dispatched by
RenderFormValueViewHelper
right before
the value of a single form element is rendered, which means before the data is
assigned to the Fluid variable provider.
The event carries the following properties:
$data
: The data array that is assigned to the Fluid variable
(
element
,
value
,
processedValue
,
isMultiValue
,
or
element
and
isSection
for sections). It can be modified,
and additional keys can be added for use in custom templates.
$element
: The form element being rendered (read-only).
$formRuntime
: The current form runtime (read-only), which gives
access to the form definition and to the values of all other form elements.
This allows developers to introduce additional data processing for complex
individual form fields.
With this change, it is possible to do the required manipulation in a single
EventListener class instead of requiring to override multiple Fluid templates.
Feature: #109849 - PSR-14 Event after form definition validation config built
A new PSR-14 event
\TYPO3\CMS\Form\Event\AfterFormDefinitionValidationConfigurationIsBuiltEvent
has been introduced as a replacement for the now
removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['buildFormDefinitionValidationConfiguration']
.
The event is dispatched by
\TYPO3\CMS\Form\Domain\Configuration\ConfigurationService
after the
form definition validation configuration has been built from the form editor
setup. It allows event listeners to add additional writable property paths for
custom form editor inspector editor implementations that do not declare their
writable property paths via the standard YAML configuration (e.g.
propertyPath
).
The event provides the following API:
getPrototypeName(): string
– The prototype name for which the
configuration was built.
getConfiguration(): array
– The built validation configuration.
setConfiguration(array $configuration): void
– Replace the
validation configuration.
Example
An example event listener that adds an additional writable property path
for a custom form element type:
With the new
AfterFormDefinitionValidationConfigurationIsBuiltEvent
,
it is now possible to extend the form definition validation configuration
using the modern PSR-14 event listener API.
Feature: #110259 - New PSR-14 AfterFileListRowPreparedEvent
A new PSR-14 event
\TYPO3\CMS\Filelist\Event\AfterFileListRowPreparedEvent
has been added to TYPO3 Core. This event is fired after a file or folder row
has been fully prepared for the Media module, right before it is
rendered into the final table row markup.
Unlike
\TYPO3\CMS\Filelist\Event\ProcessFileListActionsEvent
, which
only allows modifying the action icons in the control column, this event
provides access to the already-rendered data of every column in the row
(for example
name
,
size
or any additional metadata column
added via the column selector), as well as the row's HTML tag attributes.
This closes a gap compared to the classic record list in
Content > Records, which has offered an equivalent event
(
\TYPO3\CMS\Backend\RecordList\Event\AfterRecordListRowPreparedEvent
)
for its own rows.
An example event listener could look like this:
Example event listener class
namespaceMyVendor\MyExtension\Form\EventListener;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Filelist\Event\AfterFileListRowPreparedEvent;
finalclassAfterFileListRowPreparedEventListener{
#[AsEventListener('my-extension/after-filelist-row-prepared')]publicfunction__invoke(AfterFileListRowPreparedEvent $event): void{
$data = $event->getData();
// Modify the already-rendered value of a specific column
$data['my_column'] = 'My custom value';
$event->setData($data);
}
}
Copied!
Impact
Extension authors can now decorate or override the rendered value of any
column - not just the action icons - for a file or folder row in the File
List module, without resorting to hooks or class-name-based reflection
workarounds.
Feature: #110269 - Allow row updater registration with PHP attribute
The new PHP attribute
\TYPO3\CMS\Core\Attribute\AsRowUpdater
has been
added, which registers a class implementing
\TYPO3\CMS\Core\Upgrades\RowUpdater\RowUpdaterInterface
as a row updater
for the
databaseRowsUpdateWizard
upgrade wizard.
Row updaters are executed by the "Execute database row updates" upgrade wizard.
It iterates over all rows of all TCA tables once and hands each row to every
registered row updater that announced a potential update for the table, which
makes them the tool of choice for data migrations that cannot be expressed as a
plain SQL statement.
Until now, the list of row updaters was a hard-coded, TYPO3 Core internal
property of
\TYPO3\CMS\Core\Upgrades\DatabaseRowsUpdateWizard
. Extension
and project developers had no supported way to add their own implementation.
With the new attribute, registration is a matter of tagging the class:
Registration can alternatively be done manually with the service tag
install.rowupdater
, for example when the row updater class cannot carry
the attribute:
The identifier must be unique across all registered row updaters. It is stored
in the system registry once the row updater has been executed and is used to
determine which row updaters still need to run, so it should not be changed
afterwards.
Impact
Extensions and projects can now ship their own row updaters and have them
executed by the "Execute database row updates" upgrade wizard, both in the
Admin Tools > Upgrade backend module and with the
upgrade:run
console command.
Registered row updaters are listed with their title, can be marked as done or
undone individually, and are picked up automatically - no further configuration
is needed beyond the attribute or the service tag.
Feature: #110277 - Register file renderers as tagged services
File renderers, used for example by the
<f:media>
ViewHelper to
render audio, video or online media files, are now registered as tagged
services via dependency injection instead of the previous programmatic
registration through
\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry->registerRendererClass()
in ext_localconf.php.
A file renderer class implementing
\TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface
is
registered by adding the new PHP attribute
\TYPO3\CMS\Core\Attribute\AsFileRenderer
to the class:
The renderer priority is defined at registration time via the attribute.
Renderers with a higher priority are asked first whether they can render
a given file (
canRender()
). The file renderers shipped with TYPO3
Core are registered with the default priority
0
(previously
1
via the removed
getPriority()
method), so any custom
renderer using a priority above
0
takes precedence over them. The
order in which renderers of the same priority are evaluated is not
defined and must not be relied upon; use distinct priorities if the
evaluation order matters.
Alternatively, the service tag
fal.file_renderer
can be used
directly in Configuration/Services.yaml:
Registering file renderers as tagged services has the following
benefits over the previous programmatic registration:
The registration is resolved once at container compile time instead
of executing registration code from ext_localconf.php on
every request. Loading and validating the renderer classes per
request, as well as instantiating and sorting them by priority at
runtime, is not necessary anymore.
File renderers are proper services now and can use dependency
injection in their constructor.
The renderer priority is declared at the class itself instead of
depending on the loading order of ext_localconf.php files.
The registration is validated at container compile time: a service
tagged as
fal.file_renderer
that does not implement
FileRendererInterface
fails the container build with a
speaking exception, instead of causing errors when a file is
rendered.
Text extractors, used to extract the textual content from files, for
example for search indexing, are now registered as tagged services via
dependency injection instead of the previous programmatic registration
through
\TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorRegistry->registerTextExtractor()
in ext_localconf.php.
A text extractor class implementing
\TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorInterface
is
registered by adding the new PHP attribute
\TYPO3\CMS\Core\Attribute\AsTextExtractor
to the class:
The extractor priority is defined at registration time via the
attribute. Extractors with a higher priority are asked first whether
they can extract text from a given file (
canExtractText()
). The
PlainTextExtractor
shipped with TYPO3 Core is registered with the
default priority
0
, so any custom extractor using a priority
above
0
takes precedence. The order in which extractors of the
same priority are evaluated is not defined and must not be relied upon;
use distinct priorities if the evaluation order matters.
Alternatively, the service tag
fal.text_extractor
can be used
directly in Configuration/Services.yaml:
Registering text extractors as tagged services has the following
benefits over the previous programmatic registration:
The registration is resolved once at container compile time instead
of executing registration code from ext_localconf.php on
every request. Loading and validating the extractor classes per
request as well as instantiating them at runtime is not necessary
anymore.
Text extractors are proper services now and can use dependency
injection in their constructor.
The extractor priority is declared at the class itself instead of
depending on the loading order of ext_localconf.php files.
The registration is validated at container compile time: a service
tagged as
fal.text_extractor
that does not implement
TextExtractorInterface
fails the container build with a
speaking exception, instead of causing errors when text is
extracted.
Meta tag managers, used to manage and render the
<meta>
tags of a
page, are now registered as tagged services via dependency injection
instead of the previous programmatic registration through
\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry->registerManager()
in ext_localconf.php.
A meta tag manager class implementing
\TYPO3\CMS\Core\MetaTag\MetaTagManagerInterface
(usually by
extending
\TYPO3\CMS\Core\MetaTag\AbstractMetaTagManager
) is
registered by adding the new PHP attribute
\TYPO3\CMS\Core\Attribute\AsMetaTagManager
to the class:
The manager order is defined at registration time via the optional
before
and
after
attribute arguments, referencing the
identifiers of other managers. A manager without constraints is ordered
before the
generic
manager shipped with TYPO3 Core, which handles
every property and therefore always comes last — this matches the
previous default of
registerManager()
.
Alternatively, the service tag
metatag.manager
can be used
directly in Configuration/Services.yaml, with
before
and
after
given as comma-separated lists:
Registering meta tag managers as tagged services has the following
benefits over the previous programmatic registration:
The registration and the resolution of the
before
/
after
ordering constraints happen once at container compile time instead of
executing registration code from ext_localconf.php and
sorting the managers on every request.
Meta tag managers are proper services now and can use dependency
injection in their constructor.
The registration is validated at container compile time: a service
tagged as
metatag.manager
that does not implement
MetaTagManagerInterface
, or is missing the
identifier
tag attribute, fails the container build with a speaking exception.
Extbase now uses native PHP lazy objects
(available since PHP 8.4) to implement lazy loading of relations in domain
models annotated with the
#[Extbase\ORM\Lazy]
attribute.
Previously, two dedicated proxy classes were used, which are now deprecated:
\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage
for 1:n and
m:n relations
\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy
for 1:1 and
n:1 relations
The DataMapper now creates the following constructs instead:
For 1:n and m:n relations, a lazy ghost instance of the regular
\TYPO3\CMS\Extbase\Persistence\ObjectStorage
class. The storage
fetches its content from the persistence layer on first access.
For 1:1 and n:1 relations, a lazy proxy instance of the actual target
entity class. The related record is fetched on first access, and the proxy
then transparently forwards all calls to the mapped entity.
Concept
A native lazy object is a real instance of its class: A lazy loaded
Category
parent is a
Category
, and a lazy loaded
ObjectStorage
is an
ObjectStorage
. The PHP engine tracks the
initialization state and invokes an initializer on first property access. There
is no foreign proxy class anymore that only mimics the API of the real object.
The uid of a lazy 1:1 or n:1 relation is available without database
access, since it is known from the parent's database row and set eagerly on
the uninitialized proxy. Dirty checking, URI generation and form rendering
therefore do not trigger relation loading anymore.
Benefits
instanceof
checks against the target entity class are now true for
uninitialized lazy relations.
Native property types can be used for lazy properties. The union type
workaround is no longer needed:
Type declarations of methods consuming such relations can rely on the
actual model class.
Lazy relations no longer carry references to the
DataMapper
or the parent object as object state.
Serialization
Serializing Extbase entities and object storages is now well-defined:
Serializing a lazy relation initializes it first and serializes the
actual entity data. Previously, the internal proxy state (raw field
value, property name and a back reference to the whole parent object
graph, in older TYPO3 versions even the DataMapper state) was serialized.
ObjectStorage
now implements
__serialize()
and
__unserialize()
. The contained objects are stored as a plain list
and the internal
spl_object_hash()
based bookkeeping is rebuilt on
unserialize()
. Calling
detach()
,
contains()
or
offsetGet()
on an unserialized storage now works correctly.
Previously, the storage silently kept stale object hashes of the original
process and those methods did not work on the restored storage.
Payloads serialized with older TYPO3 versions can still be unserialized.
Impact
Lazy loading works transparently for domain models using the
#[Extbase\ORM\Lazy]
attribute. No migration is required: Existing
models keep working, the attribute remains the single way to declare a lazy
relation.
Extension authors may optionally simplify their models:
Union type declarations like
Category|LazyLoadingProxy|null
can be
reduced to the actual entity type, for example
?Category
.
Calls to
LazyLoadingProxy->_loadRealInstance()
and
instanceof LazyLoadingProxy
checks in getters can be removed, the
property value is an instance of the target class in all cases.
Whether a lazy object has been initialized can be determined with native
PHP reflection:
$isUninitialized = new \ReflectionClass(ObjectStorage::class)
->isUninitializedLazyObject($storage);
Copied!
Behavioral notes:
Calling
count()
on an uninitialized lazy object storage now fully
initializes the storage with one query. Previously, a dedicated
COUNT
query was executed without initializing the storage.
If a lazy 1:1 or n:1 relation points to a record that cannot be resolved
anymore (for example a deleted or hidden record), the parent property is
reset to
null
on first access, as before. Code holding the proxy
instance itself encounters an empty entity instance instead of the
previous
null
return value of
_loadRealInstance()
.
Feature: #110453 - Derive PageRepository instances for another Context
\TYPO3\CMS\Core\Domain\Repository\PageRepository
is bound to a
Context
when it is created. That Context decides which language overlay
and which workspace the resolved records belong to. Code that needed records
for a different language - a link to a translated page, an hreflang tag, a
menu rendered without overlays - had to clone the global Context, set the
language aspect on the clone, and then create a new instance by hand:
withLanguageAspect()
keeps every other aspect - workspace, visibility,
frontend user - of the current Context and only exchanges the language aspect.
withContext()
replaces the Context as a whole and returns the same
instance if the given Context is already the active one.
Both methods leave the original instance untouched, so a single injected
PageRepository
can serve as the starting point for any number of
derived ones:
Passing
new LanguageAspect()
resolves records without applying any
language overlay at all.
Impact
Extension authors can inject
PageRepository
through dependency injection
and derive Context-specific instances where they are needed, instead of
cloning a Context and calling
GeneralUtility::makeInstance(PageRepository::class, $context)
.
The existing constructor is unchanged and keeps working.
The
PagePathViewHelper
rendered the current page path as displayed in
TYPO3 backend modules. This information is nowadays part of the module doc
header, which is rendered by
\TYPO3\CMS\Backend\Template\ModuleTemplate
within the corresponding controller.
The abstract
AbstractBackendViewHelper
provided the helper methods
getModuleTemplate()
and
getPageRenderer()
. Both are obsolete with
the current
ModuleTemplate
view strategy and dependency injection, and
the class is no longer used as a base class within TYPO3 Core.
Impact
Using the
<f:be.pagePath>
ViewHelper in a Fluid template will trigger a
PHP
E_USER_DEPRECATED
error.
Extending
AbstractBackendViewHelper
or calling its methods
getModuleTemplate()
or
getPageRenderer()
will trigger a PHP
E_USER_DEPRECATED
error.
Both classes will be removed in TYPO3 v16.0.
Affected installations
All installations using the
<f:be.pagePath>
ViewHelper in backend Fluid
templates, or custom backend ViewHelpers extending
AbstractBackendViewHelper
.
The extension scanner reports any usage of the affected classes as strong match.
Migration
For the page path, use the doc header provided by
\TYPO3\CMS\Backend\Template\ModuleTemplate
in your backend controller,
which already displays the current page path.
Custom backend ViewHelpers should extend
\TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper
directly and
retrieve
\TYPO3\CMS\Backend\Template\ModuleTemplate
or
\TYPO3\CMS\Core\Page\PageRenderer
via dependency injection instead of the
removed helper methods.
The method
\TYPO3\CMS\Core\Utility\StringUtility::multibyteStringPad()
has been marked as deprecated and will be removed in TYPO3 v16.0.
The method was introduced to provide a multibyte-safe variant of PHP's
str_pad()
. Since PHP 8.3, the native function
mb_str_pad()
covers exactly this use case, making the TYPO3 wrapper obsolete.
Impact
Calling the method will trigger a PHP deprecation warning. It will continue
to work as before until it is removed in TYPO3 v16.0.
Affected installations
TYPO3 installations with custom extensions or code that directly call
StringUtility::multibyteStringPad()
are affected.
The extension scanner will report any usage as a strong match.
Migration
Use the native PHP function
mb_str_pad()
instead.
Note that
mb_str_pad()
throws a
\ValueError
when an empty pad
string is passed, whereas
StringUtility::multibyteStringPad()
returned
the input unchanged. If an empty pad string can occur, guard against it.
The method
\TYPO3\CMS\Core\DataHandling\DataHandler->setCorrelationId()
has been
marked as deprecated and will be removed in TYPO3 v16.0.
The correlation id of a DataHandler operation can now be handed over directly
to
DataHandler->start()
as fifth argument. This also ensures that the
correlation id is passed on to internally spawned sub instances of the
DataHandler, so all record history entries of one logical operation share the
same correlation scope. Setting the correlation id via the setter after
start()
did not propagate it to sub instances and is therefore
superseded.
Impact
Calling
DataHandler->setCorrelationId()
triggers a PHP
E_USER_DEPRECATED
error.
The extension scanner detects usages of the deprecated method as weak match.
Affected installations
All installations with custom extensions calling
DataHandler->setCorrelationId()
, usually to group the record history
entries of one logical operation. This is a rarely used API method.
Migration
Pass the
\TYPO3\CMS\Core\DataHandling\Model\CorrelationId
instance
directly to
DataHandler->start()
instead:
// Before
$dataHandler->start($dataMap, $commandMap);
$dataHandler->setCorrelationId($myCorrelationId);
$dataHandler->process_datamap();
// After
$dataHandler->start($dataMap, $commandMap, correlationId: $myCorrelationId);
$dataHandler->process_datamap();
The class
\TYPO3\CMS\Seo\XmlSitemap\AbstractXmlSitemapDataProvider
has
been marked as deprecated and will be removed in TYPO3 v16.0.
XML sitemap data providers are services now, receiving all runtime information
as
\TYPO3\CMS\Seo\XmlSitemap\XmlSitemapRequest
argument of
getSitemap()
, see Breaking: #110334 - XML sitemap data provider interface changed. The base class only
serves the previous approach of handing over the runtime information as
constructor arguments and storing the items of a sitemap in a property.
The class is kept in TYPO3 v15 to ease the transition: Data providers extending
it are still instantiated with their runtime information and keep working
unchanged, which allows extensions to support TYPO3 v14 and v15 with a single
implementation.
Impact
Instantiating a data provider extending
AbstractXmlSitemapDataProvider
triggers a PHP
E_USER_DEPRECATED
error. The sitemap itself is rendered as before.
The extension scanner detects usages of the deprecated class as strong match.
Affected installations
All installations with custom extensions providing an own XML sitemap data
provider based on
AbstractXmlSitemapDataProvider
.
Migration
Implement
\TYPO3\CMS\Seo\XmlSitemap\XmlSitemapDataProviderInterface
directly instead of extending the base class. The items are collected in
getSitemap()
instead of the constructor, and the properties of the base
class are replaced by the
XmlSitemapRequest
argument:
$this->items
becomes the items handed over to
XmlSitemap::forPage()
$this->numberOfItemsPerPage
becomes the fourth argument of
XmlSitemap::forPage()
The methods
getKey()
,
getItems()
,
getLastModified()
and
getNumberOfPages()
are not needed anymore: The returned
XmlSitemap
provides the last modification date and the number of pages,
both calculated from the items of the sitemap.
Registering a sitemap in TypoScript is unchanged: Data providers are still
referenced by their class name.
Extensions supporting TYPO3 v14 and v15 with a single implementation keep
extending
AbstractXmlSitemapDataProvider
and migrate once support for
TYPO3 v14 is dropped.
Deprecation: #110347 - LazyLoadingProxy and LazyObjectStorage
have been marked as deprecated. Lazy relations of domain model properties
annotated with
#[Extbase\ORM\Lazy]
are created as
native PHP lazy objects by the DataMapper
instead: 1:n and m:n relations as lazy ghost instances of
\TYPO3\CMS\Extbase\Persistence\ObjectStorage
, 1:1 and n:1 relations as
lazy proxy instances of the actual target entity class.
Impact
Instantiating
LazyLoadingProxy
or
LazyObjectStorage
will trigger
a PHP
E_USER_DEPRECATED
error. All three classes will be removed in
TYPO3 v16.0.
TYPO3 Core does not create instances of these classes anymore. Existing
instanceof
checks against them evaluate to
false
for relations
created by the DataMapper.
Affected installations
Installations with extensions that reference these classes, for example in
union type declarations of lazy model properties, in
instanceof
checks,
or by calling
LazyLoadingProxy->_loadRealInstance()
.
The extension scanner reports usages as weak match.
Migration
No change is required for declaring lazy relations, the
#[Extbase\ORM\Lazy]
attribute remains the single way to do so.
Union type declarations can be reduced to the actual entity type:
Calls to
_loadRealInstance()
can be removed altogether, since the
property value is an instance of the target entity class in all cases and
initializes itself transparently on first access:
Code that needs to know whether a lazy object has been initialized (for
example to avoid triggering database queries) can use native PHP reflection:
$reflection = new \ReflectionClass(ObjectStorage::class);
if ($reflection->isUninitializedLazyObject($storage)) {
// storage content has not been fetched from the database yet
}
Copied!
Deprecation: #110348 - AssetCollector media handling
The media related methods of
\TYPO3\CMS\Core\Page\AssetCollector
have
been marked as deprecated and will be removed in TYPO3 v16.0:
AssetCollector->addMedia()
AssetCollector->getMedia()
AssetCollector->hasMedia()
AssetCollector->removeMedia()
Unlike JavaScript and stylesheet assets, collected media never contributed
anything to the rendered output. The registry is a leftover of the
$TSFE->imagesOnPage
property, which was moved to the
AssetCollector
in TYPO3 v10.3 (see Deprecation: #90522 - TSFE properties regarding images) and only
ever served the "Images on this page" section of the admin panel.
Collecting this information by having every image renderer report into a
central registry is fragile: it only ever covered the code paths that
remembered to call
addMedia()
. TYPO3 dispatches
\TYPO3\CMS\Core\Resource\Event\AfterFileProcessingEvent
for every
processed file, which is a complete and more precise source for the same
information.
Impact
Calling one of the deprecated methods triggers a PHP
E_USER_DEPRECATED
error.
TYPO3 Core does not populate the registry anymore. The two places that used
to do so no longer call
addMedia()
:
\TYPO3\CMS\Frontend\ContentObject\ImageContentObject
(the
IMAGE
content object)
Consequently
AssetCollector->getMedia()
only returns entries that were
added by third-party code.
The admin panel keeps its "Images on this page" section. It now collects the
data through a PSR-14 listener on
AfterFileProcessingEvent
, which also
reports correct file sizes for images stored in remote storages and adds the
image dimensions.
Affected installations
Installations with extensions calling any of the deprecated methods, and
installations relying on Core populating the registry. Custom image
ViewHelpers or content objects that mirrored the Core behaviour by calling
addMedia()
are the most likely candidates.
The methods are not covered by the extension scanner: their names are too
generic to be matched reliably.
Migration
Remove calls to
addMedia()
and
removeMedia()
— they serve no
rendering purpose.
To collect the images processed during a request, register a PSR-14 event
listener instead. This works regardless of whether an image is rendered
through Fluid, TypoScript or custom code:
The method
\TYPO3\CMS\Core\Console\CommandRegistry->getSchedulableCommands()
has
been marked as deprecated and will be removed in TYPO3 v16.0.
Impact
Calling the method triggers a PHP
E_USER_DEPRECATED
error.
Affected installations
Installations with custom extensions that call
CommandRegistry->getSchedulableCommands()
are affected. The method is
not used by TYPO3 Core.
The extension scanner reports usages as a weak match.
Migration
Use
CommandRegistry->getSchedulableCommandsConfiguration()
to retrieve
the configuration indexed by command identifier. If command instances are
needed, retrieve them individually with
CommandRegistry->get()
:
foreach (array_keys($commandRegistry->getSchedulableCommandsConfiguration()) as $commandIdentifier) {
$command = $commandRegistry->get($commandIdentifier);
}
Both
addQueryString
and
config.linkVars
carry query
parameters of the current request over into generated page links. Until now
addQueryString.exclude
only removed a parameter from the former,
so a parameter listed in
config.linkVars
was silently added back
to the link and could not be dropped at all:
config.linkVars = print(int)
lib.link = TEXT
lib.link {
value = A link without the print parameter
typolink {
parameter.data = page:uid
addQueryString = untrusted
addQueryString.exclude = print
}
}
Copied!
With a request to /some-page?print=1 the generated link still contained
?print=1.
addQueryString.exclude
is now honored for parameters contributed
by
config.linkVars
as well, so the link above no longer carries
the print parameter. The exclusion is only applied when
addQueryString
is active —
config.linkVars
on its
own is unaffected.
Integrators who relied on
config.linkVars
overruling
addQueryString.exclude
need to remove the affected parameter from
the
exclude
list.
Important: #106584 - Asset and image ViewHelper arguments are registered
The ViewHelpers
<f:asset.css>
,
<f:asset.script>
and
<f:image>
read the arguments
href
,
src
and
alt
from the tag attributes instead of registering them. They were therefore
missing from the generated XSD schema and from the ViewHelper reference, which
made IDEs report them as unknown attributes.
All three are now registered as optional string arguments. Templates do not
need to be changed, and the arguments keep behaving as before.
One detail changes in the rendered markup of
<f:image>
: an explicitly
given
alt
attribute used to be written as the first attribute of the
<img>
tag, because unregistered arguments are added to the tag builder
before the ViewHelper renders. It is now written after
src
,
width
and
height
, which is the position the fallback to the
"alternative" metadata property has always used:
<!-- before --><imgalt="alternative text"src="image.jpg"width="400"height="300" /><!-- after --><imgsrc="image.jpg"width="400"height="300"alt="alternative text" />
Copied!
The attribute order carries no meaning in HTML, so this only affects tests and
tooling that compare rendered markup as a string.
Important: #110501 - Slashes in page titles no longer end up in generated slugs
The
slug
field of the
pages
table is generated from the page
title. Until now, a slash contained in the title was kept as-is.
As a result, a page titled Terms / Conditions was given the slug
/terms-/-conditions, and a page titled Support 24/7 was given the
slug /support-24/7. The latter additionally introduces a path
segment that does not exist as a page: the URL looks as if the page were a
sub page 7 below a page /support-24.
The TCA of
pages
now configures a slug generator replacement that
turns a slash into the fallback character -:
Every slug generated from now on is affected, no matter whether it is built
when a new page is created or when the slug is recreated from the title in
the page properties. Slugs of existing pages are only changed once they are
regenerated.
Manually entered slugs keep their slashes as before: a slash is a valid
character in a page slug, it separates the path segments of a page.
Sites relying on the previous behaviour can restore it by unsetting the
new option in Configuration/TCA/Overrides/pages.php:
The sorting order passed to
\TYPO3\CMS\Frontend\Resource\FileCollector->sort()
is no longer a plain
string but the new enum
\TYPO3\CMS\Frontend\Resource\FileCollectionSorting
with the cases
Ascending
,
Descending
and
Random
.
FileCollector
is marked as
@internal
, so this is not treated as a
breaking change. Extensions calling
sort()
directly must nevertheless
adapt their code:
// before
$fileCollector->sort('title', 'descending');
// after
$fileCollector->sort('title', FileCollectionSorting::Descending);
// when the value comes from configuration or user input
$fileCollector->sort(
'title',
FileCollectionSorting::fromKeyword($direction),
);
Copied!
FileCollectionSorting::fromKeyword()
resolves both the short and the
long keywords (
asc
,
ascending
,
desc
,
descending
,
rand
,
random
) case-insensitively, and throws a
\ValueError
for anything else.
The TypoScript option
sorting.direction
of the
FILES
content object and of the
files
data processor keeps accepting the
same keywords as before. An unknown keyword is no longer silently treated as
"ascending" though, but now results in a
\ValueError
.
15.x Changes by type
This lists all changes to the TYPO3 Core of minor versions grouped by their type.
With
config.index_externals = 1
, Indexed Search follows links to
files in the rendered page and indexes their content. So far this only worked
for files of a storage marked as public, because those are linked with a
directly accessible URL such as fileadmin/download.pdf.
Files of a storage that is not public are delivered through the file dump eID
script instead, resulting in a link like
index.php?eID=dumpFile&t=f&f=17&token=…. Such links could not be
resolved to a file and were silently skipped, even though the file was readable
on the server side.
These URLs are now resolved back to the FAL object they point to, and the file
is made available locally for the external file parsers. As a result, PDF, Word
or plain text documents of storages that are not public are indexed as well.
The indexed URL still points to the file dump URL, so search results keep
linking through the dump script.
Only file dump URLs carrying a valid token are taken into account, which means
just those generated by TYPO3 itself.
Warning
Instances relying on the previous behavior to keep documents of a storage
that is not public out of the search index should disable
config.index_externals
or remove those documents from the
index. File access itself is unaffected: the file dump script still
performs its own access checks when a search result is followed.
Important: #81619 - stdWrap "override" applies the value 0
The stdWrap property
override
replaced the current content
whenever its value was truthy after trimming. Since the string
0
is falsy in PHP, an override evaluating to a single zero
silently kept the previous content instead of replacing it. TypoScript
delivers every value as a string, which made
0
the one scalar an
override could not produce, for example when the value came from a condition
or from a nested stdWrap.
The property now leaves the content untouched only if the configured value is
unset, empty or consists of whitespace, and applies every other value,
0
included.
Instances that relied on
override = 0
being ignored - for
example to keep a disabled override in place - now render
0
instead of the previous content. Remove the property or guard it with
override.if
to keep the previous output.
Important: #83788 - Select field filters ignore diacritics
The client side filters of a
selectMultipleSideBySide
and of a
selectTree
field compared the typed text against the labels exactly as
both were written. Typing "creme" therefore did not find an option labelled
"Crème brûlée", and a category tree did not narrow to a category whose title
carries an accent.
Both the filter text and the labels are now folded before being compared: the
value is lowercased, NFD splits an accented character into its base character
plus a combining mark, and the marks of the combining diacritical marks block
are removed.
What is removed are the marks a script writes optionally: the combining
diacritical marks of Latin, Greek and Cyrillic, the Hebrew points and the
Arabic harakat. An Arabic label carrying its harakat is therefore found when it
is typed without them, and the alef variants fold onto the plain alef.
The marks that Thai, Devanagari and Japanese write obligatorily are kept,
because there a mark separates one word from another rather than accenting it.
Removing them would fold the Japanese for "school" and for "cuckoo" into one
value and leave a Thai filter narrowing nothing.
The Latin letters that Unicode does not decompose are spelled out instead, so
that an alphabet folds as a whole rather than by half: "ß" is compared as "ss",
"æ" as "ae", "ø" as "o", "ł" as "l", "þ" as "th" and so on. Typing "strasse"
therefore finds "Straße", and "lodz" finds "Łódź". Only the spellings that are
conventional are applied -- the ones that are a matter of language, such as the
German "ü" to "ue", are not, because the decomposition to "u" already settles
them.
Since NFD also decomposes precomposed Hangul syllables into jamo, and both sides
of the comparison are folded the same way, a Korean syllable now matches inside
another one sharing its leading jamo.
Characters that a reader cannot see are no longer a reason for a label not to
match: a soft hyphen and the zero width characters are dropped, and a no-break
space and the other space variants are compared as a plain space. Both arrive in
labels by pasting, and an editor has no way of telling that one is there.
Both steps live in the new module
@typo3/core/utility/text-normalizer
,
which exports a
TextNormalizer
class with the static methods
foldCaseAndDiacritics()
and
normalizeInvisibleCharacters()
. That
module is internal: it exists so that the filters of the backend treat their
input the same way, and it is not a public JavaScript API for extensions yet.
Impact
Editors searching a long option list or a large category tree find an entry
whether or not they type its accents, in both directions: "creme" finds
"Crème brûlée" and "crème" finds "Cremeschnitte".
The same comparison backs the predefined filter values of the
multiSelectFilterItems
TCA option, which are matched through the very
same code path. A filter value configured with a diacritic in order to isolate
exactly the accented options now also matches the unaccented ones.
Important: #88886 - Extbase persistence respects the language overlay type
The Extbase persistence layer previously applied "mixed" language overlay
semantics at all times when overlaying records: If a record was not available
in the requested language, the record in the default language was returned -
regardless of the fallback type configured for the site language.
Extbase now respects the overlay type (derived from the
fallbackType
setting of the site language configuration) and the configured fallback chain
when fetching aggregate roots and related child objects. Translation behavior
of Extbase queries is now consistent with the regular translation behavior of
pages and content in TYPO3:
fallbackType: strict
: Records that are not translated into the
requested language are not returned anymore.
Repository->findByUid()
on an untranslated record of the default language now returns
null
,
and untranslated related child objects (for example categories or tags) are
filtered from relations of a translated aggregate root.
fallbackType: fallback
: Unchanged behavior. Records are overlaid
with their translation if available, the default language record is used
otherwise.
fallbackType: free
: Unchanged behavior. No overlays are performed
for regular queries. Identity lookups via
Repository->findByUid()
keep resolving translations using mixed overlay semantics.
Fetching a translated record directly by its uid (for example
$postRepository->findByUid($uidOfTranslatedRecord)
) still returns the
translated record, regardless of the language of the current context.
Note that records created through Extbase in the frontend - for example via
form submissions - are persisted with
sys_language_uid=0
(default
language) unless a language is explicitly assigned. On sites using
fallbackType: strict
, such records are not visible in translated
languages until they have been translated.
To keep the previous behavior for individual queries, a custom language aspect
using mixed overlay semantics can be set on the query settings:
now treat the values of the
additionalAttributes
option the same way
Fluid's
\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder
does:
null
and
false
omit the attribute entirely
true
renders a boolean attribute, the name without a value
An empty string renders
name=""
Any other value renders
name="value"
Up to now,
null
and
false
were both rendered as
name=""
.
In the Vimeo and YouTube renderers,
null
additionally triggered the
deprecation notice
htmlspecialchars(): Passing null to parameter #1
($string) of type string is deprecated
.
This allows leaving out an attribute conditionally, which was not possible
before:
If
posterUrl
is not set, the
poster
attribute is now dropped
instead of being rendered as
poster=""
.
Attributes carrying an empty string are still rendered. In HTML,
muted
and
muted=""
mean the same thing, so dropping them would change the
meaning of the generated markup.
Important: #100638 - Extbase ignoreEnableFields applies to overlays
The Extbase query setting
setIgnoreEnableFields()
switched off the
enable fields for the records selected by the query, but not for the
translations those records are overlaid with. A hidden translation was
therefore never found, even though the query was told to ignore enable
fields:
With
languageOverlayMode = hideNonTranslated
the record was
dropped from the result entirely, while
count()
still counted it. With
the default overlay mode the untranslated default language record was returned
instead of the hidden translation.
Extbase now mirrors
ignoreEnableFields
and
enableFieldsToBeIgnored
into the visibility aspect of the context the
overlay is done with, so hidden and time-restricted translations are found as
well. Listing
'disabled'
in
setEnableFieldsToBeIgnored()
reveals
hidden translations,
'starttime'
or
'endtime'
reveals
time-restricted ones, and an empty list keeps meaning "ignore all of them".
Frontend user group restrictions (
'fe_group'
) are not part of the
visibility aspect and are still applied to translations.
Extensions that use
setIgnoreEnableFields(true)
in a multi-language
setup may see hidden translations in their result sets where they previously
saw the default language record or no record at all.
Important: #101699 - Messenger wildcard routes act as a fallback
The Symfony Messenger transport routing configured via
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']
maps a message
type to one or more transport identifiers.
So far a wildcard route - a namespace wildcard (
My\Message\*
) or the
global wildcard
'*'
- was always applied in addition to any matching
specific route. A message that matched both a specific route and a wildcard
route was therefore dispatched to both transports. Because TYPO3 ships a default
catch-all route
'*' => 'default'
, every routed message was silently sent
to the default transport as well, and routing a message to its specific
transport only required removing the wildcard route explicitly, typically in
an extension's ext_localconf.php:
// previously required to avoid the additional wildcard routingunset($GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']['*']);
Copied!
A wildcard route now acts as a fallback: it is skipped as soon as a more
specific type already matched, and only applies when no specific route matched.
This mirrors the upstream Symfony
SendersLocator
resolution, so the
shipped default
'*' => 'default'
route now behaves as a true fallback and
the manual
unset()
of the wildcard route is no longer needed:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
// handled by the "doctrine" transport only, the '*' route is skipped
\My\Extension\Message\ImportMessage::class => 'doctrine',
// fallback for every message without a more specific route'*' => 'default',
];
Copied!
Single and multiple transports per route
A routing value may be given as a single transport identifier (a string) or as
an ordered list of transport identifiers (an array) to route a single message to
several transports on purpose:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
// single transport
\My\Extension\Message\ImportMessage::class => 'doctrine',
// sent to "audit" and "doctrine", in that order
\My\Extension\Message\AuditableInterface::class => ['audit', 'doctrine'],
'*' => 'default',
];
Copied!
The legacy mapping syntax using a plain string
(
\My\Message::class => 'doctrine'
) keeps working unchanged and is treated
as the single-element array form (
\My\Message::class => ['doctrine']
).
Wildcards are supported on every namespace level
Wildcard routing is not limited to the global
'*'
route. A namespace
wildcard may be configured on any level and is likewise treated as a fallback
that is only used when no more specific route matched:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'] = [
// every message below \My\Extension\Message\Import\
\My\Extension\Message\Import\* => 'import',
// every other message below \My\Extension\Message\
\My\Extension\Message\* => 'doctrine',
// global fallback'*' => 'default',
];
Copied!
How a message is matched against the routing configuration
The routing configuration is a plain lookup map; its key order is irrelevant.
The order in which routes are considered comes from the type chain of the
dispatched message, derived from the message's PHP type (via
\Symfony\Component\Messenger\Handler\HandlersLocator::listTypes()
), not
from the order of the entries in the global configuration array.
For a dispatched message the type chain is evaluated from most to least
specific:
the concrete message class,
its parent classes (bottom-up),
its implemented interfaces,
the namespace wildcards for every namespace level (for example
My\Extension\Message\Import\*
,
My\Extension\Message\*
,
My\Extension\*
,
My\*
),
and finally the global wildcard
'*'
.
Senders are collected across every matching type in that order and deduplicated
(first occurrence wins). The first non-wildcard match therefore "locks in" the
transports and every following wildcard route is skipped. This resolution
depends solely on the class, parent and interface hierarchy of the message and
is entirely independent of the shape or ordering of the
['SYS']['messenger']['routing']
array - the configuration only provides
the mapping from a type identifier to its transports.
Impact
Installations that (knowingly or not) relied on a message being routed to both
its specific transport and the wildcard transport - most notably the default
'*' => 'default'
route - will see such a message routed to its specific
transport only. Configure the additional transport explicitly as a list value
(
\My\Message::class => ['specific', 'default']
) where the previous
fan-out is still wanted.
Providing this change on older TYPO3 versions
The change is contained in the core
\TYPO3\CMS\Core\Messenger\TransportLocator
. Projects that need the
fallback resolution on an older TYPO3 version that does not ship it yet (for
example TYPO3 v12) can obtain it in one of two ways.
Replace the core implementation via Symfony dependency injection
Copy the current
TransportLocator
into an extension, for example as
\MyVendor\MyExtension\Messenger\FallbackTransportLocator
, keeping the
#[AutowireLocator('messenger.sender', indexAttribute: 'identifier')]
constructor argument. Then re-point the messenger senders-locator to it in the
extension's Configuration/Services.yaml:
services:MyVendor\MyExtension\Messenger\FallbackTransportLocator:arguments:$sendersLocator:!tagged_locator{tag:'messenger.sender',index_by:'identifier'}# replace the core implementation used for message routingTYPO3\CMS\Core\Messenger\TransportLocator:alias:MyVendor\MyExtension\Messenger\FallbackTransportLocatorSymfony\Component\Messenger\Transport\Sender\SendersLocatorInterface:alias:MyVendor\MyExtension\Messenger\FallbackTransportLocator
Copied!
Because the messenger senders-locator is resolved through this alias, the
extension implementation is used instead of the core one and the fallback
resolution becomes available on the older core version. If the
WebhookTypesProvider
display of the resolved transports is required as
well, copy its adjusted version accordingly.
Backport the change via a Composer patch
Alternatively, apply the change to typo3/cms-core directly using a
Composer patch tool such as cweagans/composer-patches:
The patch file should contain the diff of
typo3/sysext/core/Classes/Messenger/TransportLocator.php (and, if used,
the WebhookTypesProvider.php adjustment as a
typo3/cms-webhooks
patch file) and is applied automatically on
composer install
.
Note
Due to the monorepo to composer package splitting this requires adoption of
the paths and stripping out changes in Tests/ folders, and for each
affected TYPO3 system extension a dedicated patch file needs to be created
and provided.
See Applying Core patches
for further details on how to create and apply Composer patches for TYPO3.
Important: #106903 - Form element value resolution in finishers
Finisher options substitute
{elementIdentifier}
placeholders with the
submitted value of that form element. Until now the raw submitted value was
used everywhere, so a select, radio button, multi checkbox or country select
element contributed its stored option key instead of the label an editor had
configured and translated - the subject of an email read
Request from mr
where the summary table below it read
Request from Mister
.
Options that are read by a human now resolve the placeholder through the form
element itself and therefore use the translated option label:
EmailFinisher
:
subject
,
title
,
message
and
senderName
ConfirmationFinisher
:
message
FlashMessageFinisher
:
messageBody
and
messageTitle
The same resolution already backed the summary page and the email body through
the
RenderFormValue
ViewHelper, so both places now agree on what a
submitted value looks like.
Every other finisher option keeps the submitted value, because it ends up in a
stored record, a query or a URL, where the option key is what matters. That
includes the e-mail addresses next to the sender name -
senderAddress
,
recipients
,
replyToRecipients
,
carbonCopyRecipients
and
blindCarbonCopyRecipients
- and the
redirect targets
pageUid
,
additionalParameters
and
fragment
.
The
SaveToDatabaseFinisher
in particular never resolves labels. Both of
its mapping routes store the submitted value:
elements.<element>
writes
it directly, and
databaseColumnMappings.<column>.value
resolves a
{elementIdentifier}
placeholder to the same value, so a column filled
through either route carries the same content.
whereClause
matches
against it accordingly. A stored option key stays independent of the site
language the form was submitted in and of later label changes, and remains
usable for queries and joins - resolve labels when reading the records instead.
A custom finisher opts a single option into label resolution by reading it with
AbstractFinisher->parseOptionAsDisplayValue()
instead of
parseOption()
.
Two further behaviours changed along the way:
An array value interpolated into a longer string - a multi select inside
Request for {salutation} {name}
- previously aborted the finisher
with a
FinisherException
carrying code
1519239265
. Such a value
is now joined with
,
instead. An array that contains an object which
cannot be converted to a string still throws, now with code
1787754756
.
The
implementationClassName
of the
SingleSelect
,
MultiSelect
,
RadioButton
and
MultiCheckbox
elements
changed to
GenericOptionableFormElement
, and the one of
CountrySelect
to
CountrySelect
. Both extend
GenericFormElement
, so an
instanceof
check against it keeps
matching.
Affected installations
Installations using EXT:form whose email subject, confirmation message or flash
message references a select, radio button, multi checkbox or country select
element, and installations that catch
FinisherException
with code
1519239265
.
Migration
No migration is required. Code catching
1519239265
no longer needs that
branch.
Important: #107032 - AsMessageHandler "fromTransport" property is now honoured
Symfony Messenger's
\Symfony\Component\Messenger\Attribute\AsMessageHandler
attribute provides a
fromTransport
property to bind a message handler to a
single receiving transport: the handler is then only executed for messages that
were received from that transport by a worker
(
typo3 messenger:consume <transport>
).
So far TYPO3 collected the attribute but dropped the
fromTransport
value
while building the handler map, so the property had no effect and a handler
declaring it was executed for the message regardless of the transport the
message was received from. This made a proper asynchronous send/receive setup -
where a message is consumed from a dedicated transport and only the matching
handler runs - impossible to express.
The value is now passed through to the handler descriptor and evaluated against
the envelope's
\Symfony\Component\Messenger\Stamp\ReceivedStamp
, matching
the upstream Symfony behaviour:
useSymfony\Component\Messenger\Attribute\AsMessageHandler;
// Only runs for MyMessage instances received from the "async" transport.#[AsMessageHandler(fromTransport: 'async')]final readonly classMyAsyncHandler{
publicfunction__invoke(MyMessage $message): void{
// ...
}
}
Copied!
Resolution rules:
A handler without
fromTransport
keeps running for every matching
message, regardless of the receiving transport (unchanged behaviour).
A handler with
fromTransport
only runs when the message carries a
ReceivedStamp
for one of the bound transports. A message dispatched and
handled synchronously (no
ReceivedStamp
, i.e. not received from a
worker) is not affected by the restriction and still reaches the handler.
Binding a handler to multiple transports
A handler may be bound to more than one receiving transport. As the Symfony
AsMessageHandler
attribute only accepts a single transport name per
attribute, several transports are expressed by repeating the attribute (it is
\Attribute::IS_REPEATABLE
):
useSymfony\Component\Messenger\Attribute\AsMessageHandler;
// Runs for MyMessage received from the "async" or the "priority" transport.#[AsMessageHandler(fromTransport: 'async')]#[AsMessageHandler(fromTransport: 'priority')]final readonly classMyAsyncHandler{
publicfunction__invoke(MyMessage $message): void{
// ...
}
}
Copied!
When a handler is registered via a service tag instead of the attribute, the
fromTransport
tag option additionally accepts a list of transport names
(
string
,
list<string>
or
null
):
Duplicate and empty transport names are ignored, and the handler runs at most
once per received message even when several bindings match.
Impact
Installations that already annotated a handler with
fromTransport
and
(knowingly or not) relied on the value being ignored will see that handler no
longer run for messages received from a different transport. Remove the
fromTransport
property from such a handler to restore the previous
"handle everything" behaviour.
Note
This controls the consumer side (which handler runs for a received
message). The sender side - which transport a message is routed to when
dispatched - is a separate concern: a wildcard route (a namespace wildcard
or the global
'*'
) now acts as a fallback and no longer fans out in
addition to a matching specific route (see forge#101699 and
forge#110223). Combining fallback routing on the sender side with a
fromTransport
-bound handler on the consumer side is what enables a
fully separated asynchronous send/receive setup.
Providing this change on older TYPO3 versions
The fix spans two core classes:
\TYPO3\CMS\Core\Messenger\HandlersLocatorFactory
stores the transport
binding and builds one handler descriptor per receiving transport, and
\TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass
reads the
fromTransport
tag option and carries it into that factory.
The factory is a regular service and can be swapped through
Configuration/Services.yaml. The pass, however, is a Symfony
compiler pass registered in core's Configuration/Services.php - it is
not a service and cannot be replaced through Services.yaml. As the
fromTransport
value is already dropped inside that pass on an older core,
replacing only the factory is not sufficient: the value never reaches it.
Because a second, corrected compiler pass registered from an extension would run
in addition to the core one (re-adding the handler unbound and defeating the
restriction), a local extension can only provide the corrected factory, not the
corrected pass. A complete backport therefore requires a Composer patch that
adjusts both classes in typo3/cms-core.
The patch file should contain the diff of both
typo3/sysext/core/Classes/Messenger/HandlersLocatorFactory.php and
typo3/sysext/core/Classes/DependencyInjection/MessageHandlerPass.php,
and is applied automatically on
composer install
.
Optionally replace the factory in a local extension
If the corrected pass is provided by a Composer patch, the corrected factory may
instead live in a local extension. Copy the current
HandlersLocatorFactory
into the extension, for example as
\MyVendor\MyExtension\Messenger\HandlersLocatorFactory
, and re-point the
core service to it in the extension's Configuration/Services.yaml:
services:MyVendor\MyExtension\Messenger\HandlersLocatorFactory:~# replace the core factory the messenger handlers locator is built fromTYPO3\CMS\Core\Messenger\HandlersLocatorFactory:alias:MyVendor\MyExtension\Messenger\HandlersLocatorFactory
Copied!
Because
MessageHandlerPass
resolves the factory definition by the core
class name (and the alias resolves to the extension definition), the compiler
pass adds its
addHandler()
calls to the extension implementation.
Note
Due to the monorepo to composer package splitting a patch requires adoption
of the paths and stripping out changes in Tests/ folders.
See Applying Core patches
for further details on how to create and apply Composer patches for TYPO3.
Important: #108813 - Record icons accept record objects
Rendering the icon of a record required a plain database row and its table name.
Code already working with objects implementing
\TYPO3\CMS\Core\Domain\RecordInterface
had to rebuild an array just to
render an icon, although such a record already knows its own table.
\TYPO3\CMS\Core\Imaging\IconFactory
therefore provides a new method
getIconForRecordObject()
, which takes the record object instead of a
table name and a row:
The Fluid ViewHelper
<core:iconForRecord>
gained a new argument
record
, which is interchangeable with
row
. Both accept a plain
database row as well as a record object, and exactly one of them must be given.
The argument
table
is no longer mandatory: it is required for row
arrays only and is not evaluated for record objects.
The record's raw database row is used to determine both the type icon and the
status overlays, so overlays for disabled or scheduled records are rendered
the same way as for a plain row array.
Impact
Existing usages with a table name and a plain array continue to work
unchanged. Extension authors can now pass record objects directly, which is
the recommended way when the surrounding code already works with records.
Important: #109672 - Translation domain syntax supported in resourceOverrides
The translation domain syntax (introduced in TYPO3 v14.0, see
Feature: #93334 - Translation Domain Mapping) can now also be used as the key in the
$GLOBALS['TYPO3_CONF_VARS']['LANG']['resourceOverrides']
configuration.
Previously, only file-based paths were accepted as keys. Now both the
traditional file path and the short domain syntax are valid:
Language-specific overrides also support domain syntax:
// Language-specific override with domain syntax
$GLOBALS['TYPO3_CONF_VARS']['LANG']['resourceOverrides']['de']['core.common'][]
= 'EXT:my_extension/Resources/Private/Language/de.locallang_common_override.xlf';
Copied!
Impact
Existing configurations using file-based keys continue to work without changes.
The domain syntax is an optional, shorter alternative that removes the need to
know the exact file path of the language file being overridden.
The backend Modal API (
@typo3/backend/modal
) accepts an additional shape
for the
size
option of
Modal.advanced()
. In addition to the existing
Sizes
enum (
small
,
default
,
medium
,
large
,
full
,
expand
), callers may now pass a
SizeConfig
object that
configures
width
and
height
independently.
The existing enum-based
size
values continue to work unchanged. No
migration is required for existing call sites.
New types
A new export is available from
@typo3/backend/modal
:
Size
– an enum of per-axis size tokens that map to width and
height values:
small
,
default
,
medium
,
large
,
full
.
The
SizeConfig
type uses these tokens:
type SizeConfig = { width?: Size; height?: Size };
The backend live search now surfaces files from the file abstraction
layer alongside pages and database records. A new live search provider
queries the
sys_file
and
sys_file_metadata
tables through
the existing
\TYPO3\CMS\Core\Resource\Search\FileSearchQuery
, so
matches against file names as well as indexed metadata fields like
title, description or alternative text appear in the global search.
Results respect the user's file mounts via the existing
FolderMountsRestriction
, so non-admin users only see files within
their permitted scope.
The result detail panel has been extended to show a thumbnail for
previewable files (images, videos, PDFs and online media such as
YouTube or Vimeo) together with a list of properties such as the
location, file size and last modification date.
New API on ResultItem
To support richer detail rendering, two optional fields have been added
to
\TYPO3\CMS\Backend\Search\LiveSearch\ResultItem
:
setThumbnailUrl(?string $thumbnailUrl)
— a URL to a preview
image rendered in the detail panel; falls back to the icon when
null.
addProperty(string $label, string $value)
— a key/value pair
rendered as a description list in the detail panel. Useful for
surfacing metadata like timestamps, sizes or relations.
Both fields default to empty, so existing providers continue to work
unchanged.
Impact
Backend users can now find files directly through the global live
search without switching to the media module. Custom providers can opt
into the new thumbnail and property fields to enrich their detail
views.
Important: #110233 - Search query added to AfterPageTreeItemsPreparedEvent
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent
,
dispatched by
\TYPO3\CMS\Backend\Controller\Page\TreeController
after the
backend page tree items have been resolved and prepared, now carries the current
page tree search query explicitly, instead of requiring event listeners to
extract it themselves from GET parameters of the current PSR-7 request.
A new method
getSearchQuery(): ?string
has been added, returning the
search phrase used to filter the page tree, or
null
if no search is
currently active.
Note that the
$request
argument - and therefore the return value of
getRequest()
- is now nullable as well, since the event can be
dispatched in contexts where no PSR-7 request instance is available in future.
Impact
Custom event listeners that read the page tree search phrase via
$event->getRequest()->getQueryParams()['q']
should be adapted to use
the new
$event->getSearchQuery()
method instead.
Listeners should furthermore no longer assume that
getRequest()
always
returns an instance of
ServerRequestInterface
, since it may be
null
in future major versions.
Important: #110307 - New editRecord ViewHelper argument for contextual editing
A new optional boolean argument
contextual
has been added to the
Fluid ViewHelper
<be:link.editRecord>
.
When enabled, the ViewHelper renders a
<typo3-backend-contextual-record-edit-trigger>
element instead of a
classic edit link. It builds a URL for the
record_edit_contextual
backend route and a fallback URL for the classic
record_edit
route.
It also loads the required JavaScript module
@typo3/backend/element/contextual-record-edit-trigger.js
, so custom
backend modules do not need to register that asset manually.
This makes the contextual editing behavior already used in the layout module
available to backend Fluid templates without custom PHP markup.
Existing usages of
<be:link.editRecord>
remain unchanged.
Extension authors can use contextual record editing in backend Fluid
templates, for example in custom backend previews, while keeping the shared
URL-building logic of
<be:link.editRecord>
.
Important: #110358 - Site configuration caches resolved site data
TYPO3 now caches all data needed to create
Site
entity objects in the
"core" cache: the site configuration enriched with route enhancers provided
by site sets, the composed site settings, information about invalid sets, as
well as the contents of the optional per-site files csp.yaml,
setup.typoscript, constants.typoscript and
page.tsconfig. Previously, this data was recomputed and re-read from
the file system on every request.
The
Site
entity objects themselves are still created at runtime once
per request: Expressions of configured base variants (
baseVariants
)
continue to be evaluated per request and environment as before.
Any change performed via the TYPO3 backend (Site Management
modules) or via the
SiteWriter
API invalidates this cache
automatically.
However, changes applied directly to the files csp.yaml,
setup.typoscript, constants.typoscript or
page.tsconfig within a site configuration folder (for example
config/sites/<identifier>/) now require flushing all caches to
become active - as was already the case for config.yaml and
settings.yaml before.
Important: #110385 - Language pack state stored in JSON file
The "last updated" timestamps of downloaded language packs used to be stored
in the
sys_registry
database table. When language packs are updated as
part of a deployment, the database connection may not yet be available at
that stage, which led to errors.
These timestamps are now stored in a JSON file,
language_pack_states.json, next to the downloaded language packs
themselves, in var/labels/ (Composer-based installations) or
typo3conf/l10n/ (classic installations). Existing entries are migrated
from
sys_registry
to this file once, using the upgrade wizard
Migrate the "languagePacks" registry entries to JSON storage.
This is relevant for deployment workflows that commit the downloaded
language packs to version control instead of triggering a language pack
update as part of every deployment: language_pack_states.json must
be committed (or otherwise provisioned) together with the language pack
files it describes. Otherwise, the backend's "Manage Language Packs" module
will not display accurate "last updated" information for packs that already
exist on disk.
Important: #110591 - Custom columns in the workspaces module
Listeners of the PSR-14 event
\TYPO3\CMS\Workspaces\Event\AfterDataGeneratedForWorkspaceEvent
can enrich
the records displayed in the workspaces backend module, but the module rendered a
fixed set of columns and discarded any additional data. Adding custom columns to
the module, which was possible until TYPO3 v7, therefore did not work.
The record table of the workspaces module now renders custom columns. A listener
declares one by adding an
additional
section to the records it wants to
enrich:
Records are indexed by table name and uid of the versioned record. All keys of a
column are optional:
label
- header of the column, falls back to the column identifier
value
- content of the cell, rendered as plain text
icon
- identifier of an icon rendered in front of the value
title
- title attribute of the cell
url
- wraps the content of the cell into a link to this URL
A column is displayed as soon as one record declares it. The columns are
determined from all records of the workspace and not only from those of the
current page, so the table header does not change while paging.
After TYPO3 v14.0, only new functionality with a solid migration path
can be added on top, with aiming for as little as possible breaking changes
after the initial v14.0 release on the way to LTS.
The
CacheAction
array key
href
used in cache action definitions
provided by the
\TYPO3\CMS\Backend\Backend\Event\ModifyClearCacheActionsEvent
has been
deprecated in favor of
endpoint
. The new key name better reflects the purpose of
this field, which is used as an AJAX endpoint URL. The value must be a
string
.
Impact
Cache action arrays that contain an
href
rather than an
endpoint
key
will trigger a PHP
E_USER_DEPRECATED
notice.
\TYPO3\CMS\Backend\Backend\ToolbarItems\ClearCacheToolbarItem
will automatically
migrate
href
to
endpoint
at runtime to maintain backward compatibility.
Support for the
href
key will be removed in TYPO3 v15.0.
Affected installations
Any installation that has extensions which register custom cache actions with
ModifyClearCacheActionsEvent
and
provide an action URL in the
href
array key.
Migration
Replace the
href
key with
endpoint
in any cache action array
returned from a
ModifyClearCacheActionsEvent
listener.
Extensions that still ship an ext_tables.php file will now trigger
a PHP
E_USER_DEPRECATED
error when the file is loaded during
a non-cached request or cache warm-up.
The ext_tables.php file was historically used to register backend
modules, page doktypes, user settings, and other runtime configuration.
All of these use cases now have dedicated alternatives in modern TYPO3:
User settings: Configuration/TCA/Overrides/be_users.php
(see forge#108843)
Page doktype allowed record types: Configuration/TCA/Overrides/pages.php
(see forge#108557)
Impact
A PHP
E_USER_DEPRECATED
error is triggered for every third-party
extension that still provides an ext_tables.php file whenever
ext_tables.php files are loaded without caching, for example during
cache warm-up or in a development context.
Support for ext_tables.php will be removed in TYPO3 v15.0.
Affected installations
All installations with third-party extensions that still ship an
ext_tables.php file.
Migration
Move all registration from ext_tables.php to the appropriate
configuration files.
User settings
User settings previously registered via
TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToUserSettings()
in
ext_tables.php should now be registered via
TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserSetting()
in
Configuration/TCA/Overrides/be_users.php.
Page doktypes previously registered via
PageDoktypeRegistry->add()
in
ext_tables.php should now use the TCA option
allowedRecordTypes
in Configuration/TCA/Overrides/pages.php.
The following methods in
\TYPO3\CMS\Backend\Utility\BackendUtility
have been deprecated:
getLabelFromItemlist()
getLabelFromItemListMerged()
getLabelsFromItemsList()
Their logic has been moved to the new
\TYPO3\CMS\Core\Schema\SchemaLabelResolver
class, which
provides proper dependency injection support.
Impact
Calling these methods will trigger a PHP
E_USER_DEPRECATED
error. The methods will be removed in TYPO3 v15.0.
Affected installations
TYPO3 installations with extensions that call
BackendUtility::getLabelFromItemlist()
,
BackendUtility::getLabelFromItemListMerged()
or
BackendUtility::getLabelsFromItemsList()
.
Migration
Replace calls to
\TYPO3\CMS\Backend\Utility\BackendUtility::getLabelFromItemlist()
with
\TYPO3\CMS\Core\Schema\SchemaLabelResolver->getLabelForFieldValue()
.
Replace calls to
\TYPO3\CMS\Backend\Utility\BackendUtility::getLabelFromItemListMerged()
with
\TYPO3\CMS\Core\Schema\SchemaLabelResolver->getLabelForFieldValue()
.
Replace calls to
BackendUtility::getLabelsFromItemsList()
with
\TYPO3\CMS\Core\Schema\SchemaLabelResolver->getLabelsForFieldValues()
.
Note that the new method returns an array of raw labels instead of a
comma-separated translated string — callers must handle translation
and joining themselves.
Calling
\TYPO3\CMS\Core\Utility\GeneralUtility::isOnCurrentHost()
without
providing a PSR-7
\Psr\Http\Message\ServerRequestInterface
as the
second argument is deprecated.
The method previously resolved the current host via
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
,
which hides an implicit dependency on server globals. The method signature has been
extended to accept an explicit PSR-7 request object, which should be passed instead.
Impact
A PHP
E_USER_DEPRECATED
error is triggered when
\TYPO3\CMS\Core\Utility\GeneralUtility::isOnCurrentHost()
is called without a
ServerRequestInterface
argument.
Affected installations
All installations with third-party extensions that call
\TYPO3\CMS\Core\Utility\GeneralUtility::isOnCurrentHost()
with
only one argument.
Migration
Pass the current PSR-7 request as the second argument to
\TYPO3\CMS\Core\Utility\GeneralUtility::isOnCurrentHost()
.
The PSR-7 request is available in various places, for example as an argument
in controller actions, via
$GLOBALS['TYPO3_REQUEST']
in legacy contexts,
and via
ServerRequestInterface
method parameters.
The PSR-14 events that allow listeners to inject HTML before or after
content elements have been rendered inside a backend layout column have been
deprecated and will be removed in TYPO3 v15.0:
The accompanying methods
GridColumn::getBeforeSectionMarkup()
and
GridColumn::getAfterSectionMarkup()
have also been deprecated.
These events were introduced in TYPO3 v10.3 alongside the legacy
PageLayoutView
class to enrich backend layout columns with custom
markup. They expose raw HTML strings as a column-level extension point
and force every refactoring of the page module to keep emitting that
markup at exactly the same position in the DOM. This makes it impossible
to keep them stable across versions while improving the page module:
ongoing work to rebuild the page module and its drag, drop and paste
behavior cannot proceed without locking the column's internal structure
to whatever the events happened to assume. Removing the events is a
prerequisite for that work.
The only listener that still consumed the event in the core,
PageLayoutViewDrawEmptyColposContent
, has been removed. Its job —
showing a placeholder block for backend layout cells that do not have a
configured
colPos
— is now performed in the Fluid template
PageLayout/Grid/Column.fluid.html via an
{column.unassigned}
condition. No extension action is required for this case.
Impact
Calling
\TYPO3\CMS\Backend\View\Event\AbstractSectionMarkupGeneratedEvent::setContent()
from a listener will trigger a deprecation-level log entry. The classes and the
two
GridColumn
getters will be removed in TYPO3 v15.0.
Existing listeners will keep functioning in TYPO3 v14: the dispatch sites
in
GridColumn
still fire the events and the Fluid partials still
render the resulting
{column.beforeSectionMarkup}
and
{column.afterSectionMarkup}
strings.
Affected installations
Instances and extensions that register a PSR-14 listener on
BeforeSectionMarkupGeneratedEvent
or
AfterSectionMarkupGeneratedEvent
, or that call
GridColumn::getBeforeSectionMarkup()
/
GridColumn::getAfterSectionMarkup()
.
Migration
There is no direct replacement. Listeners that decorated backend layout
columns through these events should be removed.
Calling
\TYPO3\CMS\Core\Utility\GeneralUtility::sanitizeLocalUrl()
without
passing the current PSR-7 request as the second argument is deprecated. The method
previously resolved host and site information via
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
, which falls back to server superglobals.
Passing the request explicitly allows the method to read this information from
\TYPO3\CMS\Core\Http\NormalizedParams
instead.
Impact
Calling
\TYPO3\CMS\Core\Utility\GeneralUtility::sanitizeLocalUrl()
with only one argument triggers a PHP
E_USER_DEPRECATED
error.
Affected installations
All installations that call
\TYPO3\CMS\Core\Utility\GeneralUtility::sanitizeLocalUrl()
without
passing a
\Psr\Http\Message\ServerRequestInterface
as the second argument.
The extension scanner will detect affected usages as a strong match.
Migration
Pass the current PSR-7 request as the second argument:
use TYPO3\CMS\Core\Utility\GeneralUtility;
- $url = GeneralUtility::sanitizeLocalUrl($url);+ $url = GeneralUtility::sanitizeLocalUrl($url, $request);
Copied!
Deprecation: #109548 - GeneralUtility::locationHeaderUrl() without PSR-7 request
Calling
\TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl()
without
providing a PSR-7
\Psr\Http\Message\ServerRequestInterface
as the second argument is deprecated.
The method previously resolved the current host and request directory via
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
, which
hides an implicit dependency on server globals. The method signature has been
extended to accept an explicit PSR-7 request object, which should be passed instead.
Impact
A PHP
E_USER_DEPRECATED
error is triggered when
\TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl()
is called
without a
ServerRequestInterface
argument.
Affected installations
All installations that call
\TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl()
without
passing
ServerRequestInterface
as the second argument.
The extension scanner will detect affected usages as a strong match.
Migration
Pass the current PSR-7 request as the second argument to
\TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl()
.
use TYPO3\CMS\Core\Utility\GeneralUtility;
+ use Psr\Http\Message\ServerRequestInterface;- $url = GeneralUtility::locationHeaderUrl($path);+ $url = GeneralUtility::locationHeaderUrl($path, $request);
Copied!
The PSR-7 request is available in various places, for example as an argument
in controller actions, via
$GLOBALS['TYPO3_REQUEST']
in legacy contexts,
and via
ServerRequestInterface
method parameters.
Method
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
has been deprecated.
The method abstracts server environment variables and was used to obtain request-related
data from PHP superglobals, such as the current host, URI, and site path. This information is
reliably available via
\TYPO3\CMS\Core\Http\NormalizedParams
, which is attached
as an attribute to the PSR-7 request.
Impact
Calling
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
triggers
a PHP
E_USER_DEPRECATED
error.
Affected installations
All installations that call
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
directly.
The extension scanner will detect affected usages as a strong match.
Migration
Replace calls to
\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv()
with the corresponding
\TYPO3\CMS\Core\Http\NormalizedParams
getter. A
NormalizedParams
instance is available as an attribute of the PSR-7
request:
Several properties and a methods of
\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
have been
deprecated.
Properties
ContentObjectRenderer->$lastTypoLinkResult
The property held the
\TYPO3\CMS\Frontend\Typolink\LinkResultInterface
produced by the most recent
createLink()
call. Relying on a
side-effect property that is overwritten on every subsequent link call is
fragile. Use the return value of
createLink()
directly instead.
ContentObjectRenderer->$currentRecordNumber
and
ContentObjectRenderer->$parentRecordNumber
These counters are incremented by
ContentContentObject
and
RecordsContentObject
while iterating over their record sets, and
exposed to TypoScript via
getData cobj:parentRecordNumber
.
They carry no known use case for third-party extensions and are
deprecated.
ContentObjectRenderer->$checkPid_badDoktypeList
The property was intended to cache a comma-separated list of page
doctypes that should be excluded from link target checks, but it was
never written to or read from any code path in TYPO3 itself.
Methods
ContentObjectRenderer->readFlexformIntoConf()
The method parses a FlexForm XML string or array into a flat TypoScript
configuration array. It only covered the sDEF sheet and had no
equivalent in TYPO3 core itself. Use
\TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools::convertFlexFormContentToArray()
to decode FlexForm data, and map the result into your own configuration
structure as needed.
TypoScript getData type
The
cobj:parentRecordNumber
type for the
getData function is deprecated.
It returned the value of
$parentRecordNumber
, which is now
deprecated.
ContentObjectRenderer->getRequest()
fallback to
$GLOBALS['TYPO3_REQUEST']
The
getRequest()
method falls back to
$GLOBALS['TYPO3_REQUEST']
when no request
had been set via
setRequest()
before. This fallback has been deprecated. Third-party code that
instantiates
ContentObjectRenderer
must call
setRequest(ServerRequestInterface $request)
before calling
start()
or any other method that requires the request.
Impact
Accessing
$lastTypoLinkResult
or
$checkPid_badDoktypeList
, calling
readFlexformIntoConf()
,
evaluating the
cobj:parentRecordNumber
getData type, or
triggering the
$GLOBALS['TYPO3_REQUEST']
fallback in
getRequest()
all raise
E_USER_DEPRECATED
errors at runtime.
The fallback will additionally throw an exception in TYPO3 v15 when no
request has been set.
$currentRecordNumber
and
$parentRecordNumber
carry only a
docblock
@deprecated
annotation for now — they remain functional
and do not raise runtime errors.
Affected installations
Installations with extensions that:
Read
$cObj->lastTypoLinkResult
after calling
createLink()
Read
$currentRecordNumber
,
$parentRecordNumber
, or
$checkPid_badDoktypeList
Call
$cObj->readFlexformIntoConf()
Use the
cobj:parentRecordNumber
getData type in TypoScript;
Instantiate
ContentObjectRenderer
without calling
setRequest()
before
start()
.
The extension scanner detects usages of the deprecated properties as
weak matches.
Migration
$lastTypoLinkResult
Capture the return value of
createLink()
directly:
// Before
$cObj->createLink($linkText, $conf);
$result = $cObj->lastTypoLinkResult;
// After
$result = $cObj->createLink($linkText, $conf);
Copied!
setRequest()
before
start()
Call
setRequest()
immediately after instantiation, before any other method:
// Before
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->start($data, $table);
// After
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->setRequest($request);
$cObj->start($data, $table);
Copied!
readFlexformIntoConf()
Replace with
FlexFormTools::convertFlexFormContentToArray()
and
map the decoded array into the configuration structure as needed:
// Before
$conf = [];
$cObj->readFlexformIntoConf($flexFormXml, $conf);
// After
$conf = $this->flexFormTools->convertFlexFormContentToArray($flexFormXml);
Copied!
All other deprecated items
Remove all usages. None of the remaining deprecated properties, nor
cobj:parentRecordNumber
getData type have a replacement.
Important: #109107 - Cache action endpoints should return JSON response
AJAX endpoints registered as custom cache actions via
\TYPO3\CMS\Backend\Backend\Event\ModifyClearCacheActionsEvent
should
return a JSON response containing
success
,
title
, and
message
fields.
The clear-cache toolbar now treats a missing or non-
false
success
value as a successful operation and falls back to generic notification labels
when
title
or
message
are absent. While this keeps older
endpoints working without changes, providing explicit values gives users
meaningful, context-specific feedback and ensures error conditions are surfaced
correctly.
Hint
Update any custom cache action endpoint to return a structured JSON
response:
The system extension setup (typo3/cms-setup) existed for historical reasons
as a separate package. It provided the "User Settings" backend module, where users
could change their password, name, email, language, avatar and other personal
preferences.
In modern web applications a user profile module should never be optional, so
the extension has been fully merged into the backend extension
(typo3/cms-backend). The module is now always available when the backend is
installed and can still be hidden for individual users via user TSconfig. The
separate package is no longer needed and should not be referenced in new
installations.
For Composer-based installations
The Composer package typo3/cms-backend now replacestypo3/cms-setup.
This means:
There is no need to require typo3/cms-setup in composer.json
anymore. Existing references are resolved automatically because
typo3/cms-backend declares that it replaces the package.
No manual action is required during an upgrade – Composer handles the
replacement transparently.
New projects should not add typo3/cms-setup as a dependency.
For class-based references
The following public classes have been moved and class aliases are in place for
backwards compatibility:
Extensions using the old class names will continue to work, but should be
updated to the new namespaces.
Moved event AddJavaScriptModulesEvent
A special case is
\TYPO3\CMS\Setup\Event\AddJavaScriptModulesEvent
. This file
has been moved to typo3/sysext/backend/DeprecatedClasses/Setup/Event/AddJavaScriptModulesEvent.php
and is added as a specific PSR-4 autoload entry to the Core's composer.json
map, so that the legacy event can be dispatched properly. No deprecation
message is emitted when dispatching this legacy event.
The new mechanism of using serialized JSON data for storing
backend user settings since TYPO3 14.2 has introduced a vulnerability
that stored the "password" and "verify password" input data
when changing a user's password inside the serialized user
settings representation.
These passwords are no longer stored in the database columns
be_users.uc
and
be_users.user_settings
anymore,
but may exist in database records during the period where
TYPO3 v14.2 was used.
An upgrade wizard has been added that will remove these credentials
from the serialized representation.
This upgrade wizard will detect possible records that contain
the string "password or :"password and then unserialize
the data, remove the two fields and re-serialize the data. It
is important to execute this wizard for safety. If the wizard
does not show up, no serialized credential data is found.
After TYPO3 v14.0, only new functionality with a solid migration path
can be added on top, with aiming for as little as possible breaking changes
after the initial v14.0 release on the way to LTS.
Extbase queries now support SQL expressions in
ORDER BY
clauses through a new fluent API. This enables results to be sorted using
functions such as
CONCAT
,
TRIM
, and
COALESCE
.
The following methods have been added to the
\TYPO3\CMS\Extbase\Persistence\QueryInterface
:
orderBy()
- Sets a single ordering and replaces any existing
orderings
addOrderBy()
- Adds an ordering to the existing orderings
Developers can now use SQL functions in Extbase query orderings without
resorting to raw SQL statements. This enables more flexible sorting logic
while maintaining the abstraction and security benefits of the Extbase
persistence layer.
Password generation in the backend is now driven by password policies. Each
password policy can define a generator section through a class implementing
\TYPO3\CMS\Core\PasswordPolicy\Generator\PasswordGeneratorInterface
.
The passwordGenerator field control references a policy by name via the
passwordPolicy option. The dice icon button next to the field generates
a password using the configured generator.
The field control can be added to any password field via TCA configuration,
making it available to extension developers as well.
All password policies are registered under
$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']
.
TYPO3 ships with three preconfigured policies:
default — Used for backend and frontend users
installTool — Used for Install Tool passwords
secretToken — Used for secret token fields (e.g. webhooks, reactions)
Each policy contains both a generator and a validators section. The
generator is responsible for creating passwords, while validators enforce
password requirements. They are configured independently within the same
policy.
Example
TYPO3 ships with a
PasswordGenerator
implementation that is configured like this:
Password generation for backend and frontend users is now configurable through
password policies. The Install Tool command
vendor/bin/typo3 install:password:set
also respects the configured
policy.
Feature: #78412 - Provide static TSconfig includes for be_users & be_groups
The tables be_users and be_groups are each extended by an additional field
that allows static TSconfig to be selected that is defined by extensions. These fields follow the
syntax of the tsconfig_includes field in the pages table. The following
methods are available:
The new fields can be used to define user TSconfig for specific users and
user groups provided by extensions.
Using this approach, instead of writing TSconfig directly to the database field
of be_users or be_groups, reduces the amount of configuration stored in
the database. This has several advantages:
Running a TYPO3 instance with automated deployment and Git version control
makes it easy to create or modify such an includable TSconfig snippet via a
file change. It also helps keep configuration streamlined for multiple
environments such as staging or production. Once the file is included, you
can have the same user or group configuration without having to manually change
the database for each affected user or group.
Extension authors can ship predefined user TSconfig files that can be
included by TYPO3 backend users. This also applies to local (site) packages
or your agency's base package.
Possible breaking changes in major upgrades can be handled automatically
with tools like TYPO3 Fractor (an enhancement for TYPO3 Rector). If a
breaking change occurs within user TSconfig, such a tool can automatically
upgrade the configuration, ensuring that you do not miss occurrences
in the database. This approach can reduce recurring manual work,
especially in large TYPO3 instances with many be_users or
be_groups records.
Searching through user TSconfig stored in the database is now
possible in your IDE, as all TSconfig resides in your codebase. This can
improve productivity and simplify major upgrades. You can also go
further and disable or hide the TSconfig database field in projects to
prevent saving user TSconfig to the database for users or user groups.
Feature: #87435 - Make new content element wizard items' sort order configurable
It is now possible to set the order of content elements inside the wizard tabs
in the new content element wizard by setting before and after values in
Page TSconfig.
Previously, the order of tabs could be configured but not the order of
individual content elements inside a tab. This feature extends the existing
ordering mechanism to individual content elements, following the same pattern as
tab ordering.
This eliminates the need for workarounds, such as creating custom wizard groups
to reorder elements.
Example
mod.wizards.newContentElement.wizardItems {
default.elements {
textmedia {
after = header
}
mask_article_card {
after = textmedia
}
# Multiple elements can be specified (comma-separated)
mask_article_list {
after = header,textmedia
}
# Or use before
header {
before = textmedia
}
}
}
Copied!
Impact
Integrators and developers can now set the exact order of content elements
inside each wizard tab using Page TSconfig, without needing to create custom
wizard groups. The ordering mechanism follows the same syntax and behavior as
the existing tab ordering feature (forge#71876), ensuring consistency and
familiarity.
This feature works alongside existing tab ordering: tabs can be ordered using
before and after at the group level, and content elements inside each tab
can be ordered using before and after at the element level.
If before and after configuration is not specified for elements, they retain
their default order, typically the order defined in TCA.
Feature: #88470 - Custom message in form email finisher
The email finishers EmailToSender and EmailToReceiver in the TYPO3 form
framework now support an optional custom message field. This allows editors
to add personalized text to the email sent by the form, either before or
after the submitted form values.
The message field supports rich text editing using the form-content RTE
preset, which provides formatting options such as bold, italic, links, and
lists.
A special placeholder {formValues} can be used inside the message to control
where the submitted form data table is rendered. If the placeholder is omitted,
only the custom message is shown and the form values table is hidden.
The message field is available in:
The form editor backend module
The form finisher override settings
Impact
Editors can now configure a custom message for email finishers directly in the
form editor or via finisher overrides in the form plugin. This provides more
flexibility in crafting email notifications without the need for custom Fluid
templates.
A new
\TYPO3\CMS\Core\Localization\TranslatorInterface
has been
introduced to provide a clean abstraction for translating labels in TYPO3.
The interface defines two methods:
translate()
translates a label using its identifier and domain,
supporting argument interpolation, a default fallback value, and per-call
locale overrides.
label()
resolves full TYPO3 label reference strings, for example
LLL:EXT:core/Resources/Private/Language/locallang.xlf:my.key, and
delegates to
translate()
. This method serves as the interface-based
equivalent of
LanguageService::sL()
. The key differences are that
it returns
null
when a label cannot be resolved instead of an empty
string, and supports argument interpolation, default values, and locale
overrides.
LanguageService
now implements this
interface, making it possible to type-hint against the interface instead of the
concrete class.
Example usage:
Using TranslatorInterface via dependency injection
useTYPO3\CMS\Core\Localization\TranslatorInterface;
finalclassMyController{
publicfunction__construct(
private readonly TranslatorInterface $translator,
){}
publicfunctionsomeAction(): void{
// Translate by identifier and domain
$label = $this->translator->translate(
'button.save',
'my_extension.messages',
);
// Translate by identifier and filename (discouraged)
$label = $this->translator->translate(
'button.save',
'EXT:my_extension/Resources/Private/Language/locallang.xlf',
);
// Translate with arguments
$label = $this->translator->translate(
'record.count',
'my_extension.messages',
[5],
);
// Translate with a default fallback
$label = $this->translator->translate(
'missing.key',
'my_extension.messages',
[],
'Fallback text',
);
// Label reference with arguments and default
$label = $this->translator->label(
'my_extension.messages:record.count',
[5],
'Fallback text',
);
// Resolve a full label reference string with file and LLL prefix (discouraged)
$label = $this->translator->label(
'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:button.save',
);
}
}
Copied!
The
label()
method accepts the following reference formats:
The LLL: prefix is optional and removed before resolution.
Impact
Extension developers can now type-hint against
TranslatorInterface
instead of the
concrete
LanguageService
class. This
improves testability and decouples code from implementation.
The
translate()
method of
LanguageService
has been extended
with two additional optional parameters:
$default
a fallback value returned when the label is not found
$locale
allows overriding the locale on a per-call basis
Feature: #89951 - Cleanup command for form file upload folders
A new CLI command
form:cleanup:uploads
has been introduced
to clean up old file upload folders created by the TYPO3 form framework.
When users upload files via
FileUpload
or
ImageUpload
form
elements, the files are stored in form_<hash> subfolders inside
the upload directory. Over time these folders accumulate due
to complete and incomplete form submissions.
Since uploaded files are not moved upon form submission, there is no way to
distinguish between folders from completed and abandoned submissions. The
command identifies form upload folders by their naming pattern, form_
followed by exactly 40 hexadecimal characters, and their modification time.
Folders older than a configurable retention period, by default 2 weeks, can be
removed.
You must specify at least one upload folder to scan. Since each form element
can configure a different upload folder via the
saveToFileMount
property, for example
1:/user_upload/
or
2:/custom_uploads/
, pass all relevant folders as arguments.
Usage
# Dry run: list form upload folders older than 2 weeks (default)
bin/typo3 form:cleanup:uploads 1:/user_upload/ --dry-run
# Delete folders older than 48 hours
bin/typo3 form:cleanup:uploads 1:/user_upload/ --retention-period=48
# Scan multiple upload folders
bin/typo3 form:cleanup:uploads 1:/user_upload/ 2:/custom_uploads/
# Force deletion without confirmation (useful for scheduler tasks)
bin/typo3 form:cleanup:uploads 1:/user_upload/ --force
# Verbose output shows details about each folder
bin/typo3 form:cleanup:uploads 1:/user_upload/ --dry-run -v
Copied!
Arguments
upload-folder
Combined folder identifier or identifiers to scan (required). Multiple
folders can be specified as separate arguments.
Options
--retention-period
/
-r
Minimum time in hours before a folder is considered for removal (default:
336, that is, 2 weeks).
--dry-run
List expired folders without deleting them.
--force
/
-f
Skip the interactive confirmation prompt. This is automatically set
with
--no-interaction
, for example, in the TYPO3
Scheduler.
Scheduler integration
The command is available as a Scheduler task since it is
registered via the
#[AsCommand]
attribute. Configure it to run
periodically, for example, once a day or once a week, to keep the upload
folders clean.
Impact
The new command provides a safe and configurable way to reclaim disk space
from accumulated form upload folders. The conservative default retention
period of 2 weeks ensures that files belonging to forms still being actively
worked on are not accidentally removed.
Feature: #91724 - Introduce TemplatedEmailFactory for centralized email creation
A new
TemplatedEmailFactory
class has been
introduced to provide a centralized creation of
FluidEmail
instances.
The factory provides three methods for different use cases:
create()
For backend and CLI contexts, such as login notifications, Scheduler
tasks, and the Install Tool, where only the global configuration
$GLOBALS['TYPO3_CONF_VARS']['MAIL'][...]
is used.
createFromRequest()
For frontend contexts, such as form submissions and EXT:felogin, where
site-specific email templates should be applied. It merges site settings
from
typo3/email
with the global configuration
$GLOBALS['TYPO3_CONF_VARS']['MAIL'][...]
.
createWithOverrides()
For extensions that need to provide custom template paths merged on top of
the base configuration, optionally taking a request context into account.
Two cases of template resolution are possible, ordered by priority:
Request without site attribute: 1. Provided override arguments ->
2. global configuration
Request with site attribute: 1. Provided override arguments ->
2. site settings -> 3. global configuration
Note that you can also use the numerical priority of template paths so
that site settings with a higher priority number can override paths in the
provided arguments with a lower priority number.
Site settings
A new site set,
typo3/email
, is available in EXT:core and defines the
settings below. These are applied automatically when a request with a site
attribute is passed to
createFromRequest()
or
createWithOverrides()
. This means extensions running in a frontend
context, such as EXT:form email finishers, benefit from site-specific email
configuration:
email.format
The email format to use (html, plain, both). If empty, the global
configuration is used.
email.templateRootPaths
An array of paths to email templates. These are merged with the global
mail template paths.
email.layoutRootPaths
An array of paths to email layouts. These are merged with the global mail
layout paths.
email.partialRootPaths
An array of paths to email partials. These are merged with the global mail
partial paths.
Hint
Please note that entering these paths via the site settings GUI adds
entries as a sequential array, numbered 0, 1, 2, and so on. In this case,
all paths are appended to the array, giving all entries the highest
priority.
When editing settings.yaml manually, specific numerical array keys can
be assigned.
Usage
Frontend usage (site-aware)
For frontend contexts where site-specific templates are desired, use
createFromRequest()
. Include the
typo3/email
site set in your
site configuration:
For extensions that need their own email templates merged with the global
configuration, use
createWithOverrides()
:
EXT:my_extension/Classes/Task/MySchedulerTask.php
useTYPO3\CMS\Core\Mail\MailerInterface;
useTYPO3\CMS\Core\Mail\TemplatedEmailFactory;
useTYPO3\CMS\Core\Utility\GeneralUtility;
useTYPO3\CMS\Scheduler\Task\AbstractTask;
finalclassMySchedulerTaskextendsAbstractTask{
publicfunctionsendReport(): void{
// This example shows how to use this when constructor-based// dependency injection is not possible, as in AbstractTask// (EXT:scheduler). Always use dependency injection where possible.
$mailer = GeneralUtility::makeInstance(MailerInterface::class);
$templatedEmailFactory = GeneralUtility::makeInstance(
TemplatedEmailFactory::class
);
// Merge extension-specific paths with the global configuration.// Note that if you do not pass a `$request` argument here, no site// context is evaluated. You may want to check// `$GLOBALS['TYPO3_REQUEST']` if you need this fallback, or use a// custom request object.
$email = $templatedEmailFactory->createWithOverrides(
templateRootPaths: [
20 => 'EXT:my_extension/Resources/Private/Templates/Email/',
],
layoutRootPaths: [
20 => 'EXT:my_extension/Resources/Private/Layouts/',
],
);
$email
->setTemplate('Report')
->to('admin@example.com')
->from('system@example.com')
->subject('Scheduled Report');
$mailer->send($email);
}
}
Copied!
Hint
You can use the
BeforeMailerSentMessageEvent
to change a FluidEmail object before it is sent. You can
assign Fluid variables or modify parts of the email, such as setting
From or the email subject prefix, depending on your site:
All core extensions that send emails have been migrated to use
TemplatedEmailFactory
. This includes:
EXT:form Email finishers now use
createWithOverrides()
with the
request from the form runtime, so site-specific email settings are applied
automatically.
EXT:felogin Password recovery emails now use
createWithOverrides()
, making them site-aware. The method
RecoveryConfiguration::getMailTemplatePaths()
has been removed, as
template path resolution is now handled by the factory.
EXT:backend Login notifications, failed login and MFA attempt
notifications, and password reset emails use
create()
.
EXT:install Test email sending uses
create()
.
EXT:workspaces Stage change notifications use
createWithOverrides()
.
EXT:linkvalidator Broken link report emails use
createWithOverrides()
.
EXT:reports System status emails use
create()
.
Impact
Extensions that send emails are encouraged to use the
TemplatedEmailFactory
to create
FluidEmail
instances instead of
instantiating them directly. When a request with a site attribute is passed,
template paths and format from the
typo3/email
site set are applied.
The merge priority, with the highest priority winning, is:
Global
$GLOBALS['TYPO3_CONF_VARS']['MAIL']
paths as the base
Site settings from
typo3/email
, when a site-based request is
available and site settings are applied
Caller-provided override paths when using
createWithOverrides()
Feature: #91924 - Add form element selection buttons to property grid
A new PSR-14 event
\TYPO3\CMS\Core\Routing\Event\AfterPageUriGeneratedEvent
is
dispatched in
\TYPO3\CMS\Core\Routing\PageRouter::generateUri()
.
The event provides access to the generated URI and the arguments passed to
generateUri()
. Listeners can inspect and replace the generated URI.
The
parameters
payload reflects the sanitized query arguments after
handling special parameters such as
id
and
_language
.
The event has the following methods:
getUri()
and
setUri()
getRoute()
getParameters()
getFragment()
getType()
getLanguage()
getSite()
Attention
PageRouter::generateUri()
is called from many different contexts
across TYPO3 core, not only during frontend page rendering. This event
therefore fires for URIs generated in the backend, including page preview,
FormEngine, new-record redirects, workspace preview links, XML sitemaps,
redirect source detection, webhook payloads, and error handlers, among
others.
Listeners that modify the URI must therefore be context-aware. Use
getType()
(see
\TYPO3\CMS\Core\Routing\RouterInterface
) to distinguish between
an absolute URL (
RouterInterface::ABSOLUTE_URL
) and an absolute
path (
RouterInterface::ABSOLUTE_PATH
), and use
getSite()
,
getLanguage()
, or
getRoute()
to limit
modifications to the intended context. Unconditionally replacing URIs can
break backend previews, sitemaps, or other subsystems in non-obvious ways.
When replacing the URI, listeners must ensure that the returned URI is valid in
their setup and remains routable.
Example listener registration:
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Core\Routing\Event\AfterPageUriGeneratedEvent;
useTYPO3\CMS\Core\Routing\RouterInterface;
#[AsEventListener('my-extension/after-page-uri-generated')]final readonly classMyListener{
publicfunction__invoke(AfterPageUriGeneratedEvent $event): void{
// Only act on absolute URLsif ($event->getType() !== RouterInterface::ABSOLUTE_URL) {
return;
}
// Inspect or replace $event->getUri()
}
}
Copied!
Impact
Extension authors can now react to generated page URIs for use cases such as
logging, monitoring, debugging, and URL adjustment. Because the event is
dispatched in all contexts that use
PageRouter::generateUri()
, including
the backend and various subsystems, listeners should scope their modifications
carefully.
Feature: #95910 - Add a language selector in the record history/undo view
The backend view for listing the history or audit trail of a record and for
undo or rollback functionality has been enhanced by adding a language selector
to give editors the ability to switch between different translations of a record.
The new language selection dropdown is shown only if the record is language-
aware. Available languages are determined by the translations and a
user's allowed_languages as defined by their groups.
Impact
The history/undo view of a translated record can now be accessed from the page
tree and other places where a context menu is available, not just from the
Content > Records module and the Content > Layout module language comparison view.
Feature: #97637 - Translate default value of form elements
The TYPO3 form framework now supports translating the
defaultValue
property of form elements via XLF translation files.
Previously only properties such as
label
,
placeholder
, and
rendering options could be translated through the form framework's translation
mechanism. The
defaultValue
property was always rendered as-is from the
form definition, regardless of the current frontend language.
Now,
defaultValue
is translated before rendering using the
same XLF key conventions already established for other form element
properties. The translation is resolved in the following order, with the first
match winning:
Form integrators can now provide language-specific default values for form
elements. The translation is applied automatically before rendering using the
existing translation file configuration in
renderingOptions.translation.translationFiles
.
Array-based
defaultValue
properties are intentionally excluded from
translation. These occur in multi-value elements such as
MultiCheckbox
or
MultiSelect
, where the values are option keys
that must match the configured
properties.options
exactly.
Translating them would break the value-to-option mapping. The option labels
themselves can be translated via the existing
properties.options.[*]
translation mechanism.
Feature: #97898 - TCA option isViewable for page types
A new TCA option isViewable is introduced for page types (doktype)
to configure whether a specific page type can be linked to in the page
browser and in frontend TypoLink generation.
// Disable linking for custom page type
$GLOBALS['TCA']['pages']['types']['116']['isViewable'] = false;
Copied!
By default, all page types are viewable unless explicitly set to
false
.
TYPO3 core now marks the following page types as non-viewable in TCA:
Spacer (doktype 199)
SysFolder (doktype 254)
The existing TSconfig option
TCEMAIN.preview.disableButtonForDokType
is also respected
when determining viewability in the backend page browser. If a page type
is disabled for preview via TSconfig, it is also non-viewable.
Impact
The viewability of pages can now be configured in TCA, following the same
pattern as the allowedRecordTypes option introduced in TYPO3 v14.1.
This provides a centralized way to control which page types can be
linked.
Extensions with custom page types that should not be viewable can now
configure this directly in TCA:
A new PSR-14 event
\TYPO3\CMS\Redirects\Event\RedirectIntegrityCheckEvent
has been
added. It is dispatched in
\TYPO3\CMS\Redirects\Service\IntegrityService->checkRedirectTargetIntegrity()
for each redirect record.
While the existing integrity check verifies only whether redirect sources
conflict with page URLs (self-reference), this event allows
extensions to validate redirects for other conflict types. For example, an
extension can check whether a t3://record link target still resolves correctly
or whether an external URL returns a valid response.
The event provides the following methods:
getRedirect()
: The full
sys_redirect
record as an array.
getUid()
: Convenience method returning the redirect uid as an
integer.
getPid()
: Convenience method returning the redirect pid as an
integer.
getDeleted()
: Convenience method returning the redirect deleted
as a boolean.
getDisabled()
: Convenience method returning the redirect disabled
as a boolean.
getSourceHost()
: Convenience method returning the redirect
source_host as a string.
getSourcePath()
: Convenience method returning the redirect
source_path as a string.
getIsRegExp()
: Convenience method returning the redirect
is_regexp as a boolean.
getProtected()
: Convenience method returning the redirect
protected as a boolean.
getForceHttps()
: Convenience method returning the redirect
force_https as a boolean.
getRespectQueryParameters()
: Convenience method returning the
redirect respect_query_parameters as a boolean.
getKeepQueryParameters()
: Convenience method returning the redirect
keep_query_parameters as a boolean.
getTarget()
: Convenience method returning the redirect target as
a string.
getTargetStatusCode()
: Convenience method returning the redirect
target_statuscode as an integer.
getCreationType()
: Convenience method returning the redirect
creation_type as an integer.
getOriginalIntegrityStatus()
: Convenience method returning the
redirect integrity_status as a string.
getIntegrityStatus()
/
setIntegrityStatus()
: Read or set the
integrity status. When a listener sets a non-null status, the redirect is
reported as a conflict in the
redirects:checkintegrity
command
output. Be aware that
\TYPO3\CMS\Redirects\Utility\RedirectConflict::NO_CONFLICT
can be
set as the integrity status and will not be included in the report, even
if a listener explicitly sets
\TYPO3\CMS\Redirects\Utility\RedirectConflict::NO_CONFLICT
for the redirect.
Additionally, the following new class constant has been added to allow
extensions to conveniently reuse a shared conflict status in custom event
listeners:
<?phpnamespaceMyVendor\MyExtension\EventListener;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Core\Database\ConnectionPool;
useTYPO3\CMS\Redirects\Event\RedirectIntegrityCheckEvent;
useTYPO3\CMS\Redirects\Utility\RedirectConflict;
final readonly classValidateRedirectTarget{
publicfunction__construct(
private ConnectionPool $connectionPool,
){}
#[AsEventListener('my-extension/validate-redirect-target')]publicfunction__invoke(RedirectIntegrityCheckEvent $event): void{
$target = $event->getTarget();
if (!str_starts_with($target, 't3://record')) {
return;
}
// Parse t3://record?identifier=tx_news&uid=456
parse_str((string)parse_url($target, PHP_URL_QUERY), $params);
$table = $params['identifier'] ?? '';
$uid = (int)($params['uid'] ?? 0);
if ($table === '' || $uid === 0) {
$event->setIntegrityStatus(RedirectConflict::INVALID_TARGET);
return;
}
$count = $this->connectionPool
->getConnectionForTable($table)
->count('uid', $table, ['uid' => $uid]);
if ($count === 0) {
$event->setIntegrityStatus(RedirectConflict::INVALID_TARGET);
return;
}
// Set to NO_CONFLICT. This will not be reported as a conflicting// redirect, but it clears any previously set integrity status.
$event->setIntegrityStatus(RedirectConflict::NO_CONFLICT);
}
}
Copied!
Impact
Extensions can now validate redirects during the integrity check by listening
to this event. Broken or invalid redirects are reported alongside existing
self-reference conflicts in the
redirects:checkintegrity
command
output.
Feature: #99699 - Enable rel attribute for link fields outside of the RTE
TCA type link fields now support editing and persisting the
rel
attribute in the normal link browser.
When appearance.allowedOptions contains rel, the link browser
renders a dedicated relationship input field for non-RTE link fields,
for example tt_content.header_link.
The TypoLink codec supports an optional sixth TypoLink segment for
rel
, and frontend TypoLink rendering now applies this value to
generated anchor tags.
Impact
Integrators can now use rel consistently in link browser dialogs for
both RTE and non-RTE link fields.
Existing TypoLink values without
rel
remain unchanged and continue to
work as before.
Feature: #100254 - Support download attribute in file link browser
The HTML5
download
attribute can now be configured for a link in
the file link browser. When set, the browser forces a file download
instead of navigating to the file URL.
The link browser renders a Force download checkbox for file links. When
enabled, an optional Custom filename text field appears, allowing
editors to specify an alternative filename for the downloaded file.
The TypoLink codec supports an optional seventh TypoLink segment for
download
. The value
true
produces a boolean download
attribute (
<a download>
). Any other string value produces a
named download attribute (
<a download="custom-name.pdf">
).
Example TypoLink strings:
t3://file?uid=42 - - - - - true
t3://file?uid=42 - - - - - report.pdf
Impact
Editors can now set whether a file should be downloaded or
displayed in the browser directly in the link. This works in both the RTE and non-RTE link
browser dialogs.
Existing TypoLink values without
download
remain unchanged and
continue to work as before.
Feature: #100887 - Prefer CSP hash values over nonce values
Content-Security-Policy nonce values are random tokens in each request that prevent
HTTP response caching. By collecting hash values of assets at render time
instead, responses can be cached, for example by using
lochmueller/staticfilecache
or reverse proxies, while still
enforcing a strict CSP.
Hash-based CSP is an explicit opt-in configured for a site via csp.yaml.
Nonce values remain the default when no behavior is configured.
New DirectiveHashCollection service
The new
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\DirectiveHashCollection
service is a per-request registry that collects CSP hash values for inline and
static assets during page rendering.
Both inline content and static file resources are supported:
Inline assets: the SHA-256 hash is computed over the content that appears
inside the
<script>
or
<style>
element.
Static assets: if an
integrity
attribute is already present, its
value is reused; otherwise, the file content is hashed on demand.
Style attributes: the new
f:asset.styleAttr
ViewHelper hashes
inline style values and covers the style-src-attr directive.
The collected hashes survive the frontend page cache round-trip via
\TYPO3\CMS\Frontend\Cache\MetaDataState
.
Updated Behavior class
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration\Behavior
now carries a second nullable boolean property,
$useHash
:
true
explicitly enables hash collection and CSP hash sources.
null
means off, which is the default. Hashes are not collected or
applied.
false
explicitly disables hash collection and CSP hash sources.
Configuring behavior via csp.yaml
Both
$useNonce
and
$useHash
can be set in a site's
config/sites/<site>/csp.yaml under the top-level
behavior:
key:
Setting
useHash: true
enables hash-based CSP for that site.
Setting
useNonce: false
removes nonce sources from the compiled
policy, which is required for responses to be cacheable by reverse proxies.
Updated Policy::prepare() and Policy::compile()
Both methods now accept a
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Middleware\PolicyBag
instead of separate
ConsumableNonce
,
Behavior
, and
HashCollection
arguments. The
PolicyBag
is forwarded directly
from the CSP middleware, making hash collection visible to PSR-14 event
listeners via
PolicyPreparedEvent::$policyBag->directiveHashCollection
.
The behavior resolution, applying collected hashes and suppressing nonce
sources, now happens inside
Policy::prepare()
.
New f:asset.styleAttr ViewHelper
A new ViewHelper registers inline style values using the
style-src-attr CSP directive:
The signature of
Policy::prepare()
and
Policy::compile()
has
changed to accept a
PolicyBag
. Code calling these methods directly, as
they are marked
@internal
, must be updated.
Impact
Sites that configure
behavior.useHash: true
, and optionally
behavior.useNonce: false
, in their csp.yaml can use hash-based
CSP sources. This allows HTTP responses to be cached by reverse proxies and
static file cache extensions without sacrificing Content-Security-Policy
enforcement. Sites without this configuration continue to use nonce-based CSP.
Feature: #102079 - Introduce BeforePersistingReportEvent for CSP violations
When a Content-Security-Policy violation report needs to be persisted, the
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\BeforePersistingReportEvent
can be used to provide an alternative report or to prevent a particular report
from being persisted.
A new
\TYPO3\CMS\Core\Pagination\QueryBuilderPaginator
is introduced to
enable pagination of
QueryBuilder
instances.
The paginator implements the existing
PaginatorInterface
and integrates
seamlessly with the existing
SimplePagination
and
SlidingWindowPagination
classes.
The paginated items are fetched only once per page request by storing the
result internally, avoiding double execution of the database statement.
The total item count is determined robustly using a common table expression
(CTE) wrapping the passed
QueryBuilder
instance. This approach correctly
handles advanced queries involving UNION, nested CTEs, window functions, and
grouping.
Note
The
QueryBuilderPaginator
does
not handle language overlays. Applying overlays to the result set can
lead to unexpected item count differences between pages when some records
are hidden after overlay processing. Use
QueryResultPaginator
or
ArrayPaginator
when language
overlay handling is required.
The paginator also takes full control over LIMIT and OFFSET
and does not respect any existing limit or offset constraints on the
passed
QueryBuilder
instance.
Impact
A new
QueryBuilderPaginator
is
available to paginate
QueryBuilder
result sets using
the TYPO3 pagination API.
useTYPO3\CMS\Core\Pagination\QueryBuilderPaginator;
useTYPO3\CMS\Core\Pagination\SimplePagination;
$paginator = new QueryBuilderPaginator(
queryBuilder: $queryBuilder,
currentPageNumber: $currentPage,
itemsPerPage: 10,
);
$pagination = new SimplePagination($paginator);
// Retrieve the items for the current page
$items = $paginator->getPaginatedItems();
Copied!
Feature: #102215 - ViewHelper and data structure to render srcset attribute
The srcset HTML attribute can be used to provide different image sizes to
the browser. The browser is free to choose which image size to use, which is
why the images must all be scaled versions of the same original image. Each
image in the srcset list also has a descriptor which either specifies
the absolute width of the image, for example 400w, or is a scale factor
relative to the original image size for use on high-density screens, for
example 2x.
File name notation like image@500.jpg is just a regular file name
used to indicate its pixel dimensions. The @ notation has no inherent
conversion magic, unlike the descriptors 500w and 1x.
To generate srcset attributes easily based on input, a new
data structure has been added to calculate the appropriate image sizes from a
list of descriptors. Based on these calculations, image files can be
generated using the image manipulation API.
EXT:my_ext/Classes/Service/SomeImageService.php
useTYPO3\CMS\Core\Html\Srcset\SrcsetAttribute;
// From width descriptors
$srcset = SrcsetAttribute::createFromDescriptors(['400w', '600w', '800w']);
// Or from pixel density descriptors (a reference width must be supplied)
$srcset = SrcsetAttribute::createFromDescriptors(
['1.5x', '2x', '3x'],
800
);
// Add image URIsforeach ($srcset->getCandidates() as $candidate) {
// Generate scaled image here using $candidate->getCalculatedWidth()// Set URI of the generated image
$candidate->setUri($generatedImageUri);
}
// Render srcset attribute
$srcsetString = $srcset->generateSrcset();
Copied!
To generate srcset attributes in Fluid templates, a new ViewHelper has also
been introduced.
The new ViewHelper f:image.srcset simplifies previous manual implementations
that used
f:uri.image
for each image size. This now makes it easier
to provide images in different dimensions based on a single image.
Feature: #102430 - Flush cache tags for file and folder operations
This feature is guarded by the frontend.cache.autoTagging feature toggle and
is currently experimental. The core flushes cache tags automatically for all
kinds of records when they are created, changed, or deleted. This is not the
case for files and folders. This feature adds cache tag handling for file and
folder operations when they are created, changed, or deleted. File metadata
changes are now handled correctly as well. This will lead to a better editor
experience if cache tags are used correctly.
Impact
Integrators and extension developers can now add sys_file_${uid} and
sys_file_metadata_${uid} as cache tags, and they are flushed correctly by
the TYPO3 core when an editor interacts with them in the Media module.
Feature: #102790 - Line wrapping option for code editor
A new TCA appearance option lineWrapping has been added for the
codeEditor render type. When enabled, long lines are wrapped inside
the editor instead of requiring horizontal scrolling.
TYPO3 now supports ICU MessageFormat for translations. This enables the proper handling
of plural forms, gender-based selections, and other locale-aware formatting
in language labels.
ICU MessageFormat is an internationalization standard that allows messages to
contain placeholders that can vary based on parameters such as quantity,
gender, or other conditions. This is particularly useful for proper
pluralization in languages with complex plural rules.
The format is detected automatically when named arguments, that is,
associative arrays, are used in translation calls. If the message contains ICU
patterns like {count, plural, ...} or {name}, and named arguments are
provided, the ICU MessageFormatter is used automatically.
Language file format
ICU MessageFormat strings are stored as regular translation strings in XLIFF
files:
<?xml version="1.0" encoding="UTF-8"?><xliffversion="1.2"xmlns="urn:oasis:names:tc:xliff:document:1.2"><filesource-language="en"datatype="plaintext"original="locallang.xlf"><body><!-- Simple plural form --><trans-unitid="file_count"><source>{count, plural, one {# file} other {# files}}</source></trans-unit><!-- Plural with zero case --><trans-unitid="item_count"><source>{count, plural, =0 {no items} one {# item} other {# items}}</source></trans-unit><!-- Combined placeholder and plural --><trans-unitid="greeting"><source>Hello {name}, you have {count, plural, one {# message} other {# messages}}.</source></trans-unit><!-- Gender selection --><trans-unitid="profile_update"><source>{gender, select, male {He} female {She} other {They}} updated the profile.</source></trans-unit><!-- Simple named placeholder --><trans-unitid="welcome"><source>Welcome, {name}!</source></trans-unit></body></file></xliff>
Copied!
PHP usage
Use named arguments in an associative array to trigger ICU
MessageFormat processing:
Using ICU MessageFormat with LanguageService
useTYPO3\CMS\Core\Localization\LanguageServiceFactory;
$languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class)
->createFromUserPreferences($backendUser);
// ICU plural forms: use named arguments
$label = $languageService->translate(
'file_count',
'my_extension.messages',
['count' => 5]
);
// Result: "5 files"// Combined placeholder and plural
$label = $languageService->translate(
'greeting',
'my_extension.messages',
['name' => 'John', 'count' => 3]
);
// Result: "Hello John, you have 3 messages."// sprintf-style still works with positional arguments
$label = $languageService->translate(
'downloaded_times', // Label: "Downloaded %d times"'my_extension.messages',
[42] // Positional arguments use sprintf
);
// Result: "Downloaded 42 times"
Copied!
Using ICU MessageFormat with LocalizationUtility
useTYPO3\CMS\Extbase\Utility\LocalizationUtility;
// Use named arguments for ICU format
$label = LocalizationUtility::translate(
'file_count',
'MyExtension',
['count' => 1]
);
// Result: "1 file"
Copied!
Fluid usage
In Fluid templates use named arguments in the arguments attribute:
<!-- ICU plural forms with named arguments --><f:translatekey="file_count"arguments="{count: numberOfFiles}" /><!-- Combined placeholder and plural --><f:translatekey="greeting"arguments="{name: userName, count: messageCount}" /><!-- Gender selection --><f:translatekey="profile_update"arguments="{gender: userGender}" /><!-- sprintf-style with positional arguments still works --><f:translatekey="downloaded_times"arguments="{0: downloadCount}" />
Copied!
ICU MessageFormat syntax reference
Plural forms:
{variable, plural,
=0 {zero case}
one {singular case}
other {plural case}
}
Copied!
Select (gender/choice):
{variable, select,
male {He}
female {She}
other {They}
}
Copied!
Number formatting:
{count, number} - Basic number
{price, number, currency} - Currency format
Copied!
The # symbol in plural patterns is replaced by the actual number.
Impact
This feature provides a standards-based approach to pluralization that:
uses the well-tested ICU library, via PHP's intl extension
handles locale-specific plural rules
supports complex pluralization for languages such as Russian and Arabic
is backward compatible; existing sprintf-style translations will continue to
work
The system detects which format to use based on arguments:
Named arguments (associative array): Uses ICU MessageFormat
Feature: #103504 - New ContentObject PAGEVIEW introduced the
PAGEVIEW
cObject
for frontend rendering. It is a powerful alternative to
the
FLUIDTEMPLATE
cObject, allowing a full page to be rendered with
less configuration.
The content elements are attached to the new
\TYPO3\CMS\Core\Page\ContentArea
object, which also contains all
column-related information and configuration.
This is useful for frontend rendering because an
element may need to know its rendering context. Knowing
this information, an element can, for example, decide not to render the
Header
partial if it is in a sidebar content area.
ContentArea
objects are added to the
view either by variable name defined in
contentAs
or,
if not defined, content. Content elements can then be accessed via the
records
property.
ContentArea
objects contain
backend layout-related configuration, such as
content restrictions. These allow
further validation such as whether a content type is
valid.
Therefore
{content.main.records}
can be used to get content
elements from the main content area. main is the identifier as defined in
the page layout, and content is the default variable name.
Important
ContentArea
objects are attached in
the
ContentAreaCollection
, which
implements the PSR-11
\Psr\Container\ContainerInterface
to allow
access to the content areas using
get()
. To optimize performance
ContentArea
objects are
instantiated only when accessed (lazy loading).
Accessing a
ContentArea
using
{content.main}
makes the following information available, as defined in
the page layout:
identifier
- The column identifier
colPos
- The defined colPos
name
- The descriptive name, which might be a locallang key
allowedContentTypes
- The defined allowedContentTypes
disallowedContentTypes
- The defined disallowedContentTypes
slideMode
- The defined
ContentSlideMode
, which defaults to
ContentSlideMode::None
configuration
- The complete content area-related configuration
records
- The content elements as
Record
objects
The following example renders the content elements of a page which has only
a single column:
mod.web_layout.BackendLayouts {
default {
title = Default
config {
backend_layout {
colCount = 1
rowCount = 1
rows {
1 {
columns {
1 {
name = Main Content Area
colPos = 0
identifier = main
}
}
}
}
}
}
}
}
The introduction of the new
f:render.contentArea and
f:render.record ViewHelpers means that manually
iterating over content elements is no longer necessary. All the content elements
in a content area can be rendered with a single ViewHelper call:
ContentArea
helps the
AfterContentHasBeenFetchedEvent
to manipulate content elements in an area by
providing context.
Impact
It is now possible to access all the content elements on a page, grouped by their
column, as well as having all the column-related information and
configuration available. In addition to reduced configuration effort,
different rendering is possible for an element depending on context.
Example
A content element template using a Default layout that renders the
Header partial only if the content element is not in the sidebar column.
A new TypoScript setting
plugin.tx_indexedsearch.settings.pagination_type
has been
introduced to set the pagination implementation used by
EXT:indexed_search.
Available values:
simple
: uses
SimplePagination
and renders
all result pages.
slidingWindow
: uses
SlidingWindowPagination
and
limits the displayed page links as set in
plugin.tx_indexedsearch.settings.page_links
.
The default is
simple
to preserve existing behavior.
Integrators can switch to
slidingWindow
to make
page_links
effective for indexed_search result browsing.
Impact
Integrators can now switch between core pagination implementations using
TypoScript, without having to use custom PHP code.
Advanced, fully-customized pagination logic can still be implemented using
ModifySearchResultSetsEvent
.
Feature: #105649 - New PSR-14 CustomFileSelectorsEvent
A new PSR-14 event
\TYPO3\CMS\Backend\Form\Event\CustomFileSelectorsEvent
has been added. It is dispatched in
FilesControlContainer
during the rendering of selectors for relations to sys_file_references.
To modify the selectors used to add files, the following methods are
available:
getSelectors()
: Get all selectors
setSelectors()
: Set all selectors
getJavascriptModules()
: Get all JavaScript modules
setJavascriptModules()
: Set all JavaScript modules
getTableName()
: Get the table name of the current record
getFieldName()
: Get the field name of the element
getDatabaseRow()
: Get the raw database row
getFieldConfig()
: Get the TCA configuration of the current field
getFileExtensionFilter()
: Get the allowed and disallowed file
extensions
getFormFieldIdentifier()
: Get the DOM object ID used in the form
It is now possible to modify file selectors using the new PSR-14 event
CustomFileSelectorsEvent
. This is
especially useful for integrating a DAM system.
Feature: #105708 - Multiple file upload for EXT:form elements
The TYPO3 form framework now supports multiple file uploads in the
FileUpload
and
ImageUpload
form elements. This allows users
to select and upload multiple files using a single form field.
The implementation follows the same security patterns as Extbase file upload
handling. It uses HMAC-signed deletion requests to ensure secure file removal.
Configuration
To enable multiple file uploads for a form element, set the
multiple
property to
true
in your form definition:
The
multiple
option is also available in the Form Editor backend
module as a checkbox in the element's inspector panel.
The
allowRemoval
property enables users to remove previously uploaded
files before submitting the form. When enabled, a Remove checkbox is
displayed next to each uploaded file.
File count validation
The existing
Count
validator can now be used with
FileUpload
and
ImageUpload
elements to limit the number of
uploaded files:
The file input field renders with the HTML5
multiple
attribute
Previously uploaded files are displayed in a list with individual remove
checkboxes
Users can select multiple files in the browser's file picker dialog
On the summary page, multiple files are displayed as a list
File deletion
The implementation uses HMAC-signed deletion requests similar to Extbase file
handling. Each uploaded file displays a checkbox that, when checked, marks the
file for removal on form submission. The deletion data is signed with an HMAC
to prevent manipulation.
A new ViewHelper,
<formvh:form.uploadDeleteCheckbox>
, is available
for custom templates:
Adapting custom finishers for multiple file uploads
When
multiple
is enabled on a
FileUpload
element, the value
returned by
$formRuntime[$element->getIdentifier()]
is an
ObjectStorage<FileReference>
instead of a single
FileReference
. Custom finishers that process file uploads need to be
adapted to handle both cases, single and multiple uploads.
The following example shows the pattern used in the core
EmailFinisher
and
DeleteUploadsFinisher
:
useTYPO3\CMS\Core\Resource\FileInterface;
useTYPO3\CMS\Extbase\Domain\Model\FileReference;
useTYPO3\CMS\Extbase\Persistence\ObjectStorage;
useTYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
useTYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
classMyFinisherextendsAbstractFinisher{
protectedfunctionexecuteInternal(): void{
$formRuntime = $this->finisherContext->getFormRuntime();
foreach (
$formRuntime->getFormDefinition()->getRenderablesRecursively()
as $element
) {
if (!$element instanceof FileUpload) {
continue;
}
$file = $formRuntime[$element->getIdentifier()];
// Single file upload: value is a FileReferenceif ($file instanceof FileReference) {
$this->processFile($file->getOriginalResource());
}
// Multiple file upload: value is an ObjectStorage of FileReferencesif ($file instanceof ObjectStorage) {
foreach ($file as $singleFile) {
if ($singleFile instanceof FileReference) {
$this->processFile(
$singleFile->getOriginalResource()
);
}
}
}
}
}
privatefunctionprocessFile(FileInterface $file): void{
// Your custom logic, e.g. move, copy, attach, etc.
}
}
Copied!
Per-element validation with ObjectStorageElementValidatorInterface
When a form field value is an
ObjectStorage
, for example, a
multiple-file upload, the
ProcessingRule
must decide how to call each
registered validator:
Collection-level validators (default) receive the entire
ObjectStorage
. Use this for
validators that check the collection as a whole, such as
CountValidator
for the minimum
or maximum number of items.
Element-level validators receive each item individually. Use this for
validators that inspect a single item, such as
MimeTypeValidator
or
FileSizeValidator
.
To mark a validator as element-level, implement the marker interface
ObjectStorageElementValidatorInterface
:
useTYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
useTYPO3\CMS\Form\Mvc\Validation\ObjectStorageElementValidatorInterface;
finalclassMyFileValidatorextendsAbstractValidatorimplementsObjectStorageElementValidatorInterface{
publicfunctionisValid(mixed $value): void{
// $value is a single element from the ObjectStorage,// e.g. a FileReference - not the whole collection.
}
}
Copied!
For single-value fields, that is, non-
ObjectStorage
values, the
interface has no effect. Validators are always called with the field value
directly.
Impact
Form integrators can now create forms that accept multiple file uploads
without custom extensions
The
FileUpload
and
ImageUpload
elements support the new
multiple
property
All existing finishers, EmailFinisher, SaveToDatabaseFinisher, and
DeleteUploadsFinisher, automatically support multiple file uploads
Email templates display multiple files as a list of filenames
The summary page displays multiple images as a gallery and multiple files
as a list of filenames
Feature: #105742 - Synchronized manipulation of all crop variants
The image manipulation wizard allows images to be cropped to multiple
crop variants. When many variants were present, each had to be edited
individually, requiring the same changes to be applied multiple times.
This was particularly tedious when an editor needed identical crop values
across all image variants.
This feature introduces a checkbox that allows editors to crop all image variants
simultaneously. This checkbox is available if all crop variants share
identical aspect ratios and configuration (except for the title).
excludeFromSync is a new sub-option of the cropVariants TCA/TCEFORM
configuration array which allows developers to exclude specific crop variants
from synchronized cropping.
This is useful, for example, when adding a special crop variant for a list
view that has a different configuration, while still allowing other crop
variants to be synchronized.
Example
The following example defines standard crop variants for a Bootstrap-based
template.
All crop variants are configured identically (except for the title), which
enables synchronized cropping.
An additional crop variant for the tx_news list view is defined.
The option excludeFromSync = 1 ensures that this variant is excluded
from synchronization.
TCEFORM.sys_file_reference.crop.config.cropVariants {
xxl {
title = Very Large Desktop
selectedRatio = NaN
allowedAspectRatios {
# [...] array of defined aspect ratios (identical!)
}
}
xl {
title = Large Desktop
selectedRatio = NaN
allowedAspectRatios {
# [...] array of defined aspect ratios (identical!)
}
}
# [...]
}
# Override for news extension
TCEFORM.tx_news_domain_model_news.fal_media.config {
overrideChildTca.columns.crop.config.cropVariants {
listview {
title = List view
selectedRatio = default
excludeFromSync = 1
allowedAspectRatios {
# [...] array of custom aspect ratio definitions# (or identical aspect ratios, but not considered for# synchronized cropping)
}
}
}
}
Copied!
Impact
Editors can now apply changes to image aspect ratios and cropping to
multiple matching crop variants in the image manipulation wizard.
Specific cropVariants can be excluded from synchronization.
Feature: #105827 - Search in backend page tree and live search can find pages by their frontend URI
The backend page tree search functionality has been enhanced to allow users to
enter a full URI such as https://mysite.example.com/de/any/subtree/page/,
which shows the matching page in the result tree.
Multiple URIs can be separated with commas (,), just like multiple page IDs.
It is also possible to combine different search input:
Live search returns both the default language page derived from the URI and the
matching translated page.
Configuration
Search by frontend URI is enabled by default and can be controlled in two ways,
similar to search by translation:
User TSconfig
Administrators can control the availability of frontend URI search with
user TSconfig:
# Disable searching by frontend URI for specific users/groups
options.pageTree.searchByFrontendUri = 0
Copied!
User preference
Individual backend users can toggle this setting in the page tree toolbar menu.
The preference is stored in the backend user's configuration, allowing each
user to customize search behavior.
Impact
Editors can now easily locate a backend page when only the frontend URI is
available. Permissions to view or edit the page are respected. Invalid or
non-matching URIs are ignored.
Feature: #105827 - New PSR-14 ModifyConstraintsForLiveSearchEvent
A new PSR-14 event
\TYPO3\CMS\Backend\Search\Event\ModifyConstraintsForLiveSearchEvent
has been added to TYPO3 Core. This event is dispatched in the
LiveSearch
class and allows
extensions to modify the
CompositeExpression
constraints collected in an array before execution.
This makes it possible to add additional constraints to the main query
constraints, combined with a logical OR. These constraints could
not previously be accessed by the existing event
ModifyQueryForLiveSearchEvent
.
The event provides the following methods:
getConstraints()
: Returns the current array of query constraints
(composite expressions).
addConstraint()
: Adds a single constraint.
addConstraints()
: Adds multiple new constraints.
getTableName()
: Returns the table for which the query is executed
(for example, pages or tt_content).
getSearchDemand()
: Returns the search demand.
Hint
Constraints are intended to be added only. This ensures that
security-related mandatory constraints added by Core or extensions cannot
be negatively affected. For this reason, there is no way to remove a
constraint after it has been added.
Example
The corresponding event listener class:
<?phpnamespaceVendor\MyPackage\Backend\EventListener;
useTYPO3\CMS\Backend\Search\Event\ModifyConstraintsForLiveSearchEvent;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Core\Database\ConnectionPool;
final readonly classPageRecordProviderEnhancedSearch{
publicfunction__construct(
private ConnectionPool $connectionPool,
){}
#[AsEventListener('my-package/livesearch-enhanced')]publicfunction__invoke(
ModifyConstraintsForLiveSearchEvent $event,
): void{
if ($event->getTableName() !== 'pages') {
return;
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
// Add a constraint so that pages marked with "show_in_all_results=1"// will always be shown.
$constraints[] = $queryBuilder->expr()->eq(
'show_in_all_results',
1,
);
$event->addConstraints(...$constraints);
}
}
Copied!
Core itself uses this event to allow searching for frontend URIs in the
backend page tree.
Impact
A new PSR-14 event is now available for adding constraints to the live search
query. These constraints are combined with a logical OR.
Feature: #106153 - Improve DebugExceptionHandler with copy functionality
The debugging exception handler, which can be configured for backend and
frontend error reporting, provides a large stack trace with details about
an error.
This is often essential when reporting bugs in TYPO3 or debugging custom
code.
The output has now been improved:
Each stack trace segment's file name and the line number where the error
occurred now has a "Copy path" button. Clicking it copies the full
path, file name, and line number to the browser clipboard.
The bottom of the page shows two buttons: one toggles the output above
to hide or reveal the file contents, and the other copies the entire
stack trace in plain text format so that it can be forwarded in error
reports.
A brief section explains what a "stack trace" is, and a jump link is
available to go from the top of the page to the export section.
Hint
The "copy to clipboard" functionality is based on JavaScript. Some
browsers, such as Firefox, allow access to the clipboard only when
the site is accessed via https. If copying fails, the condensed
output that would have been written to the clipboard is shown instead
in a box below so it can be copied manually.
Errors and their stack traces can now be copied and forwarded much more
easily for support requests, without the need to save an HTML file or take
screenshots.
File names and line numbers of errors can also be copied easily and
inserted into an IDE to jump directly to the relevant code.
Feature: #106261 - Align command line arguments of message consumer with Symfony original
This change aligns the command line arguments of the TYPO3 Console
messenger:consume command with the original Symfony Messenger
implementation.
The following new options have been added:
--limit
/
-l
: Limits the number of received messages.
--failure-limit
/
-f
: Limits the number of failed
messages the worker can consume.
--memory-limit
/
-m
: Sets the memory limit available
to the worker.
--time-limit
/
-t
: Sets the time limit in seconds
during which the worker can handle new messages.
--bus
/
-b
: Specifies the name of the bus to which
received messages are dispatched.
--all
: Consumes messages from all receivers.
--keepalive
: Uses the transport keepalive mechanism, if
implemented.
Scheduler integration
The command can be configured as a scheduler task in TYPO3, enabling
automated consumption of messages from the configured transports. This is
particularly useful for processing asynchronous messages in the
background.
This integration helps projects adopt asynchronous message handling by
providing a reliable way to process messages without manual
intervention. Messages can be dispatched asynchronously during normal
request handling and consumed in the background by the scheduler task,
improving application performance and user experience.
Important
The
messenger:consume
command blocks other scheduler tasks
from executing while it is running. It is therefore strongly
recommended to set the
--time-limit
option to a value lower
than the scheduler's cron interval.
For example, if the scheduler runs every 5 minutes (300 seconds),
set the time limit to 240 seconds (4 minutes) to ensure the task
completes before the next scheduler run and allows other tasks to
execute.
<trans-unitid="settings.my.enumSetting.enum.optionA"><source>Option A (localized)</source></trans-unit><trans-unitid="settings.my.enumSetting.enum.optionB"><source>Option B (localized)</source></trans-unit>
Copied!
Map-style enum declaration in settings.definitions.yaml
settings:my.enumSetting:type:stringdefault:optionAenum:optionA:'LLL:my_extension.labels:settings.custom.optionA'# Explicit LLL referenceoptionB:'Literal Option B'# Literal labeloptionC:# Key-only map-style entry, falls back to enum value "optionC"optionD:''# Empty label stays empty
Copied!
Referenced label in labels.xlf
<trans-unitid="settings.custom.optionA"><source>Option A (localized)</source></trans-unit>
Copied!
If you want to work with automatically derived keys in the set
labels.xlf, for example
settings.<settingKey>.enum.<enumValue>, omit enum labels in YAML
and use list-style enum declarations.
Impact
Integrators can localize enum options consistently using the same
resolution behavior as other setting labels.
Feature: #106681 - Support relative date formats in DateRange validator
The
DateRange
validator of the form extension now supports relative
as well as absolute dates in Y-m-d format.
This allows form integrators to define dynamic date constraints that are
evaluated at runtime, such as ensuring a date of birth is at least
18 years in the past or that a date is in the future.
The following relative expressions are supported and follow the syntax of
strtotime()
:
Named dates:
today
,
now
,
yesterday
,
tomorrow
Relative offsets:
-18 years
,
+1 month
,
-2 weeks
,
+30 days
These expressions can be used in the
options.minimum
and
options.maximum
properties of the
DateRange
validator.
Example
Ensure that a date of birth is at least 18 years in the past:
type:Dateidentifier:date-of-birthlabel:'Date of birth'validators:-identifier:DateRangeoptions:maximum:'-18 years'
The form editor in the TYPO3 backend has been updated to accept these
relative expressions in the date range fields. The HTML
min
and
max
attributes on the rendered
<input type="date">
element are automatically resolved to
absolute Y-m-d dates at render time.
Impact
Form integrators can now use relative date expressions in the
DateRange
validator configuration. Existing form definitions
using absolute dates will continue to work without changes.
Feature: #106828 - Add user TSconfig to define default live search action
To use preview for a custom record, a valid preview configuration
must exist for the table in TCEMAIN.preview.
Impact
The default actions of live search results can now be configured with
user TSconfig. Integrators can define global and table-specific behavior
for search results, improving backend workflows.
The default behavior for pages has been changed to layout to improve
the user workflow.
Feature: #107003 - Add event to change record data in list view
A new PSR-14 event
\TYPO3\CMS\Backend\RecordList\Event\AfterRecordListRowPreparedEvent
has been added. This event is dispatched in
DatabaseRecordList
and allows
extensions to modify the data used to render a single record in the list
view.
The event allows the following properties to be modified:
data: The row fields as an array. The following fields are available:
_SELECTOR_: The checkbox element
icon: The icon
__label: Special field that contains the header
_CONTROL_: The row action buttons
_LOCALIZATION_: The current language
_LOCALIZATION_b: The translated language
rowDescription: The row description
header: The header. This field is used only if __label is not
set. Use __label instead.
uid: The record UID (read-only)
tagAttributes: The HTML tag attributes of the row. The following
attributes are available:
class
data-table
title
The corresponding event listener class:
useTYPO3\CMS\Backend\RecordList\Event\AfterRecordListRowPreparedEvent;
useTYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener('my-package/backend/my-listener-name')]finalclassMyEventListener{
publicfunction__invoke(AfterRecordListRowPreparedEvent $event): void{
$data = $event->getData();
$tagAttributes = $event->getTagAttributes();
// Modify the row data and tag attributes here.
$event->setData($data);
$event->setTagAttributes($tagAttributes);
}
}
Copied!
Impact
The new PSR-14 event can be used, for example, to modify the title link in
the record list.
Feature: #107058 - Simplify registration of a custom form element
The registration of custom form elements in the TYPO3 Form Framework has
been simplified. Previously, registering a custom form
element required subscribing to the JavaScript event
view/stage/abstract/render/template/perform
to render the element
in the Form Editor stage area.
Custom form elements can now be registered without
custom JavaScript code. The Form Editor automatically uses a generic
Web Component to render form elements in the stage area.
To use this simplified registration method, omit the
formEditorPartials
configuration in your form element's YAML
definition. The Form Editor then automatically renders the element using
the built-in
<typo3-form-form-element-stage-item>
web component,
which provides:
Element type and identifier display
Element label with required indicator
Validator visualization
Support for select options (SingleSelect, MultiSelect,
RadioButton, Checkbox)
Support for allowed MIME types (FileUpload, ImageUpload)
Element toolbar
Hidden state visualization
The generic rendering automatically extracts and displays relevant
information from the form element configuration without requiring a
custom template or JavaScript code.
Impact
Extension developers can now register custom form elements with minimal
configuration. By omitting the
formEditorPartials
configuration, the Form Editor automatically renders the element using a
generic Web Component, eliminating the need for:
Custom Fluid templates in
Resources/Private/Backend/Partials/FormEditor/Stage/
Custom JavaScript code subscribing to
view/stage/abstract/render/template/perform
Manual element rendering logic
This significantly reduces the complexity and maintenance burden when
creating custom form elements that do not require special visualization
in the Form Editor.
For custom form elements that require specialized rendering or custom
interactions in the stage area, the
formEditorPartials
configuration can still be used to provide custom Fluid templates, which
continue to work as before.
TYPO3 now tracks the history of all Extbase domain entities by
listening to Extbase persistence events and storing them in the
sys_history
table. This provides a comprehensive audit trail for
all frontend and backend operations on Extbase entities without requiring
any code changes.
The feature leverages TYPO3's existing
RecordHistoryStore
infrastructure and integrates seamlessly with the backend record history
functionality.
The history tracking captures:
Create operations: when entities are persisted for the first time
Update operations: when existing entities are modified
Delete operations: when entities are removed from persistence
All operations are tracked with their proper user context (frontend users,
backend users, anonymous operations) and include full entity data
snapshots.
Configuration
History tracking is disabled by default. It can be enabled with the
feature toggle extbase.enableHistoryTracking (available via
System > Settings > Feature toggles).
Once the feature toggle is enabled, history tracking is active for all
Extbase domain model storage tables. It can then be disabled via TCA
on a per-table basis:
Defining this at the TCA level (instead of TypoScript persistence
configuration) means that it can be configured per table and
evaluated consistently in all contexts (backend, frontend, CLI).
If a third-party extension enables history tracking via TCA, it can be
disabled using TCA overrides. Disabling the feature toggle also disables
all history tracking, even for tables configured with
enableHistoryTracking => true.
In addition, the following PSR-14 event listeners can be deregistered or
replaced at instance level:
extbase-history-tracker-persisted
extbase-history-tracker-updated
extbase-history-tracker-removed
Note
Enabling history tracking can generate a large number of history
entries for Extbase entities. These entries are mixed with regular
editorial changes made in the TYPO3 backend (FormEngine).
Important
All changes to Extbase entity data are logged, including full initial
data snapshots. This may have implications for GDPR / DSGVO and other
security-related data handling requirements. Data may need to be
pruned regularly. It is advisable to disable history tracking for
tables containing sensitive data. For this reason, the feature toggle
is disabled by default and requires explicit activation.
Impact
Changes to all Extbase domain entities can now be tracked
in the
sys_history
table, making them visible in the
backend record history. This requires enabling the feature toggle
extbase.enableHistoryTracking (default: false).
This feature provides administrators and developers with full visibility
into data changes without requiring interface implementations or code
modifications.
Technical details
The implementation consists of a PSR-14 event listener
ExtbaseHistoryTracker
which automatically registers for the following Extbase persistence
events:
EntityAddedToPersistenceEvent
EntityUpdatedInPersistenceEvent
EntityRemovedFromPersistenceEvent
All entities with valid TCA configuration are tracked automatically. This
uses the Extbase DataMap API, TCA Schema API, and RecordHistoryStore API.
Feature: #107771 - Make rel attribute in external links configurable
For security reasons, external links that open in a new window should be
generated with rel="noopener" to prevent the opened page from
accessing the originating document via JavaScript's
Window.opener object.
TYPO3's default behavior is to add rel="noreferrer" to all such
links. This automatically implies rel="noopener" but is even more
restrictive, as it also prevents the HTTP Referer header from being sent to
the opened page. This may be too strict and therefore undesirable for some
website owners.
This feature introduces a new TypoScript option
config.linkSecurityRelValue to define the rel
attribute for external links. The default behavior remains
rel="noreferrer", but by setting the TypoScript property to
noopener, all external links are generated with
rel="noopener" instead.
The feature respects existing the individual settings of a link. Any existing
rel="noopener" and rel="noreferrer" values from other
sources are preserved.
Impact
A new TypoScript configuration option
config.linkSecurityRelValue is available and can be set to
noreferrer (default) or noopener.
This setting affects all external links with target="_blank".
Feature: #107802 - Support username and password in Redis session backend
Since Redis 6.0, it is possible to authenticate against Redis using
both a username and a password. Before that, authentication was possible
by password only. This change means the TYPO3 Redis session backend
can be configured as follows:
The password configuration option of the Redis session backend is now
typed as array|string. Setting this configuration option to an array is
deprecated and will be removed in TYPO3 v15.0.
A new authorization mechanism has been introduced for Extbase controller
actions using PHP attributes. Extension authors can now implement
declarative access control logic on action methods using the
#[Authorize]
attribute.
The
#[Authorize]
attribute supports multiple authorization
strategies:
Built-in checks:
Require frontend user login via
requireLogin
Require specific frontend user groups via
requireGroups
Custom authorization logic:
Dedicated authorization class (recommended for complex logic)
Public controller method (for simple checks)
Multiple
#[Authorize]
attributes can be stacked on a single
action. All authorization checks must pass for access to be granted. If
a check fails, a
PropagateResponseException
is thrown with an
HTTP 403 response, which stops the Extbase dispatch process.
The requireGroups parameter accepts an array of frontend user group
identifiers. Groups can be specified either by their UID (recommended) or
by their title. If multiple groups are specified, the user must be a
member of at least one group (OR logic).
namespaceMyVendor\MyExtension\Controller;
usePsr\Http\Message\ResponseInterface;
useTYPO3\CMS\Extbase\Attribute\Authorize;
useTYPO3\CMS\Extbase\Mvc\Controller\ActionController;
classMyControllerextendsActionController{
// Recommended: Use group UIDs#[Authorize(requireGroups: [1, 2])]publicfunctionadminListAction(): ResponseInterface{
// Only accessible to users in groups 1 or 2return$this->htmlResponse();
}
// Alternative: Use group titles (not recommended)#[Authorize(requireGroups: ['administrators', 'editors'])]publicfunctioneditorListAction(): ResponseInterface{
return$this->htmlResponse();
}
// Mixed: UIDs and titles can be combined (not recommended)#[Authorize(requireGroups: [1, 'editors'])]publicfunctionmixedListAction(): ResponseInterface{
return$this->htmlResponse();
}
}
Copied!
Note
It is strongly recommended to use group UIDs instead of group titles.
Group titles can be changed by editors, which would break the authorization
logic. Group UIDs are stable and should be preferred.
Custom authorization class
For complex authorization logic, create a dedicated authorization class.
This class supports dependency injection and can be reused across
controllers. The class must be publicly available in the DI container,
which can be achieved by annotating it with
#[Autoconfigure(public: true)]
.
namespaceMyVendor\MyExtension\Controller;
useMyVendor\MyExtension\Authorization\MyObjectAuthorization;
useMyVendor\MyExtension\Domain\Model\MyObject;
usePsr\Http\Message\ResponseInterface;
useTYPO3\CMS\Extbase\Attribute\Authorize;
useTYPO3\CMS\Extbase\Mvc\Controller\ActionController;
classMyControllerextendsActionController{
#[Authorize(requireLogin: true)]#[Authorize(requireGroups: [1, 2])]#[Authorize(callback: [MyObjectAuthorization::class, 'checkOwnership'])]publicfunctioneditAction(MyObject $myObject): ResponseInterface{
// Only accessible to logged-in users in groups 1 or 2 who own the objectreturn$this->htmlResponse();
}
}
Copied!
Authorization checks can be combined within a single attribute:
By default, the authorization check throws a
PropagateResponseException
with an HTTP
403 response. This response can be handled by the TYPO3 page error
handler configured in site settings.
The PSR-14 event
BeforeActionAuthorizationDeniedEvent
can be used to provide a custom PSR-7 response, which is then returned by
Extbase.
namespaceMyVendor\MyExtension\EventListener;
usePsr\Http\Message\ResponseFactoryInterface;
usePsr\Http\Message\StreamFactoryInterface;
useTYPO3\CMS\Extbase\Authorization\AuthorizationFailureReason;
useTYPO3\CMS\Extbase\Event\Mvc\BeforeActionAuthorizationDeniedEvent;
finalclassCustomAuthorizationResponseListener{
publicfunction__construct(
private readonly ResponseFactoryInterface $responseFactory,
private readonly StreamFactoryInterface $streamFactory,
){}
publicfunction__invoke(
BeforeActionAuthorizationDeniedEvent $event,
): void{
// Customize response based on failure reason
$message = match ($event->getFailureReason()) {
AuthorizationFailureReason::NOT_LOGGED_IN =>
'Please log in to access this page',
AuthorizationFailureReason::MISSING_GROUP =>
'You do not have permission to access this page',
AuthorizationFailureReason::CALLBACK_DENIED =>
'Access to this resource is denied',
};
$response = $this->responseFactory->createResponse()
->withHeader('Content-Type', 'text/html; charset=utf-8')
->withStatus(403)
->withBody($this->streamFactory->createStream($message));
$event->setResponse($response);
}
}
Copied!
Security considerations
Warning
When using the
BeforeActionAuthorizationDeniedEvent
event:
Do not perform state changes or modify domain objects in the
event listener. The authorization check happens before the action
is executed, and changes could lead to inconsistent data.
Do not use Extbase persistence (for example, repository
operations or persist calls) in the event listener, as this may
result in unintended side effects.
Custom PSR-7 responses should only be used for uncached Extbase
actions. For cached actions, the custom response may be cached
and served to all users regardless of their authorization
status. Ensure proper cache configuration when customizing
authorization responses.
Impact
Extension authors can now implement secure, declarative authorization
checks for Extbase controller actions using the
#[Authorize]
attribute.
Feature: #107887 - New "Latest backend logins" widget
A new dashboard widget Latest backend logins has been
introduced to display recent backend user logins in
the TYPO3 Dashboard. This allows administrators to quickly monitor user
activity and track recent backend access patterns without navigating
the system log.
The widget provides a configurable interface where administrators can
set the number of logins to display, offering flexibility in monitoring
scope.
Each login entry shows:
The backend user avatar and name
The login time
Key benefits:
Displays recent backend user logins with user details and
timestamps
Provides direct access to login monitoring without navigating the
system log
Offers configurable display limits for different monitoring needs
Enhances security monitoring with quick access to login patterns
Impact
This feature improves administrative oversight by providing immediate
visibility of recent backend user activity in the
dashboard.
Feature: #107906 - Recently opened documents widget
A new Recently Opened Documents dashboard widget has been introduced to
display documents that are currently open or recently accessed in the
TYPO3 backend. This allows editors and administrators to quickly return to
their work and access frequently edited content without navigating through
the page tree or search.
The widget provides a configurable interface where users can set the number
of documents to display, offering flexibility based on their workflow
needs. Each document entry shows the record icon and title for easy
identification and quick access.
Key benefits:
Displays recently opened documents with icons and titles
Provides quick access to ongoing work without searching
Offers configurable display limits for different workflow needs
Shows document type icons for visual identification
Improves editing efficiency by reducing navigation time
Displays documents in reverse chronological order (most recent first)
The widget retrieves documents from the FormEngine session data, ensuring
that only currently open or recently accessed documents are displayed.
Deleted records are filtered out to maintain data accuracy.
Impact
This feature improves editorial efficiency by providing immediate access to
recently opened documents in the dashboard interface, reducing
the time spent navigating through the backend to resume work.
Feature: #107940 - Introduce report about content type usage
A new Content statistics module has been introduced in the TYPO3
backend under Reports. This module provides information about the
usage of content elements in the TYPO3 site.
Overview
The overview displays all available content element types along with the number
of times each type is used.
All the associated fields of the element are listed, including key details such
as:
The field type
Whether it is marked as required
Whether it can be configured as excludable via user group permissions
Detail view
The detail view of each content element type lists all the relevant records
that are not marked as deleted.
Impact
The new report offers a convenient way to analyze and optimize content
structures within a TYPO3 installation.
It helps administrators and developers to:
Identify unused content element types
Understand which fields belong to specific content element types
Gain insights into the overall configuration and diversity of content
elements
Feature: #108345 - Allow extensions without ext_emconf.php in classic mode
Initially ext_emconf.php was the only file providing
extension metadata. Since the introduction of composer.json,
now mandatory for extensions,
there are now two files containing a lot of redundant data.
This is now resolved by allowing an extension's composer.json
to contain information that was previously defined in
ext_emconf.php:
The version number can be set in extra.typo3/cms.version or alternatively
in the "version" field in composer.json.
For third-party extensions to be compatible with TYPO3 classic mode,
this version must now be set to the same version previously defined in ext_emconf.php
and should match the version in the Git tag, for example when publishing to Packagist.
Fixture extensions used in tests can set any version number, for example 1.0.0,
but a version number must still be provided to avoid deprecation messages.
During testing the version number is not evaluated.
TYPO3 Core extensions may omit the version number
in composer.json because their version number is derived via
\TYPO3\CMS\Core\Information\Typo3Version
.
Extension state and update exclusion
The former state property in ext_emconf.php was used for multiple purposes.
In composer.json, this is now represented by dedicated metadata instead
of a single field.
Supported extension stability values are expressed as version suffixes, for example:
Values from the former state field that are not supported by Composer stability
can be expressed as build metadata by appending +... to the version string.
This replaces overloading the former state field for update handling.
Dependencies on other TYPO3 extensions
ext_emconf.php had a property for specifying dependencies
on other extensions by referencing the extension key and an optional
range of versions.
composer.json also contains a field for specifying dependencies
using a Composer package name with a version range.
However, there is no direct way to distinguish whether such a package name
refers to another TYPO3 extension or to a regular Composer package
that should be installed from Packagist.
TYPO3, however, needs to know which other extensions an extension depends on
in order to resolve the extension loading order correctly.
Therefore, TYPO3 must know which package names refer to TYPO3 extensions
and which refer to regular Composer packages. In Composer mode, this can
be resolved automatically.
In classic mode, TYPO3 now recognizes several categories:
TYPO3 framework packages shipped by the core
Composer packages already installed and shipped with TYPO3
Composer packages provided by other loaded extensions via
providesPackages
Because of this, extension authors do not need to repeat such package names
in providesPackages.
Extensions still need to declare Composer packages that they themselves provide
when loaded in classic mode. For those entries, providesPackages can also
define a relative path to a Composer vendor directory. If that directory contains
a Composer-generated autoload.php, TYPO3 includes it early during bootstrap.
This makes it possible to both declare Composer packages and bootstrap
their autoloader in a standardized way.
Here is an example of an extension that ships a local Composer vendor directory:
In this example, the package symfony/dotenv is provided by the extension itself
in TYPO3 classic mode, and TYPO3 will include
Resources/Private/Php/ComposerVendor/autoload.php early if it is a
Composer-generated autoload file.
The Composer package names typo3/cms-core and vendor/other-example
are assumed to refer to TYPO3 extensions, and TYPO3 guarantees that vendor/example
is loaded after vendor/other-example. Otherwise, an error is thrown if
the extension vendor/other-example does not exist in the system.
Packages that are already shipped by TYPO3 or already provided by another loaded
extension do not need to be listed in providesPackages.
Even if an extension does not depend on any Composer packages,
it is still required to specify providesPackages in composer.json
as an empty object to ensure future compatibility with TYPO3 classic mode
and to avoid deprecation messages in TYPO3 v14.
The PHP dependency is kept as package metadata so TYPO3 classic mode
can still evaluate PHP version requirements. However, it is ignored for
extension dependency ordering.
Be aware that keeping ext_emconf.php, while no longer directly required
by TYPO3, may still be necessary for some tools,
such as Tailor or TYPO3 TER. Therefore, for the time being, it is recommended
to keep the file and ensure that its information stays in sync
with composer.json as outlined above.
However, TYPO3 will not evaluate ext_emconf.php anymore if the required
metadata is correctly defined in composer.json and package metadata can be
derived from it.
Impact
Extensions can now omit ext_emconf.php in TYPO3 classic mode.
A deprecation message is shown during cache warm-up when ext_emconf.php
is present and composer.json is not yet future-proof
because it does not contain the required metadata definitions.
Feature: #108557 - TCA option allowedRecordTypes for page types
// Allow any record on that page type.
$GLOBALS['TCA']['pages']['types']['116']['allowedRecordTypes'] = ['*'];
// Allow only specific tables on that page type.
$GLOBALS['TCA']['pages']['types']['116']['allowedRecordTypes'] = [
'tt_content',
'my_custom_record',
];
Copied!
The array can contain a list of table names or a single asterisk entry (*)
to allow all record types.
By default, only the tables pages, sys_category, sys_file_reference, and
sys_file_collection are allowed if this option is not overridden.
The defaults are extended if TCA tables enable the option
ctrl.security.ignorePageTypeRestriction. Again, this is not considered if
allowedRecordTypes
is set. These tables must then also be configured
there.
Impact
The allowed record types for pages can now be configured in TCA. This
centralizes the configuration for page types and further reduces the need for
ext_tables.php, which was used previously.
The page module's content element preview functionality has been
enhanced to provide editors with a better visual representation of content
elements in the backend.
Sanitized HTML rendering for content
Content elements with HTML in the bodytext field (such as text, text & images)
now display sanitized HTML in the page module preview instead of plain text.
A new
\TYPO3\CMS\Core\Html\PreviewSanitizerBuilder
has been introduced that creates a sanitizer
specifically for backend previews. This sanitizer:
Removes clickable links (unwraps
<a>
tags while preserving
their content)
Removes heading tags (
<h1>
through
<h6>
) while
preserving their content
Allows safe HTML formatting (bold, italic, lists, etc.)
Enhanced bullet list preview
Content elements of type "bullet list" now render as HTML lists in the preview.
Harmonized menu element rendering
Preview rendering for menu content elements and "insert records" elements
has been harmonized to match the layout used in the record selector wizard.
This provides a consistent experience across different parts of the backend.
Impact
These enhancements improve the editorial experience in the TYPO3
backend by providing clearer, more informative content previews. Editors can now:
See formatted HTML content as it will appear to users
Quickly identify bullet list structure and content
Feature: #108581 - Record type specific label configuration
Previously, the TCA label configuration (ctrl['label'], ctrl['label_alt'],
and ctrl['label_alt_force']) applied globally to all record types in a
table. This meant that all content elements in
tt_content
, regardless of
their CType, displayed the same field(s) as their label in the backend.
It is now possible to define type-specific label configuration in the
TCA types section. These settings override global ctrl label
configuration for a record type:
label - Primary field used for the record title
label_alt - Alternative field(s) used when label is empty (or as
additional fields)
label_alt_force - Force display of alternative fields alongside the
primary label
This is especially useful for tables like
tt_content
where different
content element types may benefit from showing different fields. For example, an
"Image" content element could display the image caption, while a "Text" element
would show the header field.
All types use header as the primary label field (from ctrl['label']).
The article type displays the teaser field if header is
empty.
The event type displays header together with event_date and
location (since label_alt_force is enabled).
When adding a new record type to a table, label configuration can
be provided as the third argument $additionalTypeInformation of
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addRecordType
.
Tables with multiple record types can now define more specific and descriptive
labels for each type in the backend user interface. This improves usability and
clarity for editors by making it immediately obvious which type of record is
being displayed.
This feature is especially useful for:
Content element tables such as
tt_content
with different CType
values
Tables with different record types serving different purposes
Plugin records with varying functionality for each type
Any table where the record type changes the record's purpose or meaning
All occurrences of
\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle()
in TYPO3
automatically benefit from this feature without any code changes. This includes
record lists, page trees, history views, workspaces, and similar backend
modules.
Functionality such as FormEngine records cannot benefit from this option yet,
as FormEngine does not support the Schema API yet.
Feature: #108648 - Option to modify src attribute for Vimeo/YouTube
The new configuration option srcAttribute for the YouTubeRenderer
and VimeoRenderer can be used to modify the previously hard-coded src
attribute in the resulting iframe HTML code. This can be useful if
the iframe should not be immediately loaded because of privacy concerns. An
alternative such as data-src can be used in the initial
HTML markup.
The
typo3/cms-form
extension has been extended to include a new database
storage adapter (
DatabaseStorageAdapter
),
allowing form definitions to be stored in the database table
form_definition
instead of relying on file system storage only.
Form definitions can now be stored in three ways:
Database storage (new, recommended) – stored as records in the
form_definition
table
Extension paths – shipped with extensions (read-only or configurable)
Storage adapter architecture
The storage layer uses the Chain of Responsibility pattern. Each storage
adapter implements the
StorageAdapterInterface
and declares
which persistence identifiers it can handle via its
supports()
method.
The
StorageAdapterFactory
iterates
through all registered adapters sorted by priority and delegates to the first
matching adapter.
Three adapters are shipped:
DatabaseStorageAdapter
(priority 100)
ExtensionStorageAdapter
(priority 75)
FileMountStorageAdapter
(priority 50,
deprecated)
Database table
form_definition
A new TCA-managed table
form_definition
stores the form definitions
with the following fields:
label
– the human-readable form name
identifier
– the unique form identifier (e.g., contact-form)
configuration
– the full form definition as JSON
Records are read-only in the standard TCA editing interface. All write and
delete operations go through
DataHandler
, ensuring proper
permission checks, history tracking, and hook execution.
Form Manager wizard
A new Storage wizard step lets editors choose the storage type (file
mount, extension, database) when creating or duplicating forms. When only
one storage adapter is accessible, the step auto-advances.
The Form Manager now also shows a record history action in the dropdown menu
for database-stored forms, linking to the TYPO3 record history module.
Record list integration
Two event listeners customize the record list of
form_definition
records:
The standard edit action is replaced with a link to the Form Editor
module.
The standard delete action is removed. Deletion is only possible
through the Form Manager.
Clicking the record title opens the Form Editor instead of the TCA
editing form.
Creation of
form_definition
records via the "New Record" wizard
is denied via page TSconfig:
A new CLI command
form:definition:transfer
allows form
definitions to be transferred between any two storage backends. This is particularly useful for
migrating file-based forms to database storage via the command line.
# Transfer all forms from file mounts to database
bin/typo3 form:definition:transfer --source=filemount --target=database
# Transfer a specific form by its identifier
bin/typo3 form:definition:transfer --source=extension --target=database --form-identifier=contact
# Move forms (transfer + delete from source)
bin/typo3 form:definition:transfer --source=filemount --target=database --move
# Dry-run: preview what would be transferred without making changes
bin/typo3 form:definition:transfer --source=filemount --target=database --dry-run
# Transfer to a specific target location (PID for database storage)
bin/typo3 form:definition:transfer --source=filemount --target=database --target-location=0
Copied!
Available options:
--source
– source storage type (database, extension,
filemount)
--target
– target storage type
--target-location
– target storage location
--form-identifier
– transfer only a specific form
--move
– delete the source form after successful transfer
--dry-run
– preview without making changes
Configuration
Backend users must have table access rights for the
form_definition
table.
Important
Permission model differences between file-based and database storage
The file-based storage allows granular access control through TYPO3 file
mounts. Different backend user groups can be restricted to different
storage folders effectively isolating which forms each group can see and
edit.
Database storage currently relies on TCA table permissions
(
tables_select
/
tables_modify
for
form_definition
).
This means that all backend users who have table access can see
all database-stored form definitions — there is no
equivalent to the file mount–based isolation yet.
A dedicated access control mechanism (comparable to file mount
isolation) for database-stored forms is planned but not yet implemented.
If your installation depends on separate permission boundaries for
different editor groups, it is recommended to not migrate to database
storage at this time and continue using file-based storage until the
permission feature is available.
Impact
Editors can store new form definitions in the database by selecting the "Database"
storage type in the Form Manager creation wizard.
File-based storage (file mounts) will remain functional during the deprecation
period but will trigger
E_USER_DEPRECATED
errors. See
Deprecation: #108653 - Form file-based storage deprecated for migration instructions. Existing
file-based forms are not affected by this change.
Extension-based storage will continue to work without change.
Feature: #108720 - QR code button for frontend preview
A new QR code button has been added next to the View button in various backend
modules. Clicking the button opens a modal displaying a scannable QR code for
the frontend preview URI.
The button is available in the following locations:
Content > Web module (Layout view and Language Comparison view)
Web > List module
Web > View module
Web > Workspaces module
Inside a workspace a QR code contains a special preview URI that will
work without backend authentication. This makes it easy to share workspace
previews with colleagues and clients, or to quickly check draft versions on a
mobile device by scanning the code.
The QR code can be downloaded as PNG or SVG from the modal.
Impact
Editors can benefit from a streamlined workflow by sharing page previews and
testing pages on mobile devices. Workspace-aware preview URIs eliminate the
need to be logged in when scanning the QR code, making it particularly useful
for reviewing processes involving external stakeholders.
Feature: #108726 - Add PSR-14 events ModifyRenderedContentAreaEvent and ModifyRenderedRecordEvent
The
\TYPO3\CMS\Fluid\Event\ModifyRenderedRecordEvent
allows developers to
intercept the rendering of individual records in Fluid templates and modify the output.
This depends on records being rendered with the new
<f:render.contentArea>
, see
Introduce Fluid f:render.contentArea ViewHelper, or
<f:render.record>
ViewHelpers in Fluid templates, see
Introduce Fluid f:render.record ViewHelper.
Note that any alterations will be output as is and will not be escaped. If you
process insecure content inside an event listener, be sure to escape it properly,
for example by applying
htmlspecialchars()
to it.
<f:render.contentAreacontentArea="{content.left}"/>
or
{content.left -> f:render.contentArea()}
Copied!
The ViewHelper also supports wrapping each content element with additional markup
if combined with the <f:render.record> ViewHelper:
MyPage.fluid.html
<f:render.contentAreacontentArea="{content.main}"recordAs="record">
before {record.fullType}
<f:render.recordrecord="{record}" />
after {record.fullType}
</f:render.contentArea>
Copied!
Impact
Theme creators are encouraged to use the
<f:render.contentArea>
ViewHelper
to allow other extensions to modify the output via EventListeners.
The
typo3 fluid:analyze
console command is introduced, which analyzes
Fluid templates in the current project for correct Fluid syntax and reports
deprecations that are emitted during template parsing.
Usage:
vendor/bin/typo3 fluid:analyze
Copied!
Example output:
[DEPRECATION] packages/myext/Resources/Private/Templates/Test.fluid.html: <my:obsolete> has been deprecated in X and will be removed in Y.
[ERROR] packages/myext/Resources/Private/Templates/Test2.fluid.html: Variable identifiers cannot start with a "_": _temp
Copied!
In its initial implementation, the command automatically finds all Fluid
templates within the current project based on the *.fluid.* file extension
(see
Feature: #108166 - Fluid file extension and template resolving)
and analyzes them. By default, TYPO3 system extensions are skipped. This can
be adjusted by specifying the
--include-system-extensions
CLI option.
The following errors and deprecations are currently supported:
Fluid syntax errors (for example, invalid nesting of ViewHelper tags)
Usage of invalid ViewHelpers or ViewHelper namespaces
If exceptions are caught during the parsing process of at least one template,
the console command will have a return status of 1 (error). Otherwise, it will return 0
(success). This means that deprecations are not interpreted as errors.
This should make it possible to use the command in CI workflows of most
projects, since deprecated functionality used by third-party templates will
not make the pipeline fail.
Verbose output allows users to get feedback on the analyzed templates
and the number of errors and deprecations, or success.
Integration with other tools
The command also supports input of a template string via STDIN as well as
machine-readable output as JSON. This enables better integration with other
development-related tools.
{
"identifier": "template__5adb1a7702b9dcbf",
"path": "php:\/\/stdin",
"errors": [
{
"file": "\/var\/www\/html\/vendor\/typo3fluid\/fluid\/src\/Core\/Parser\/TemplateParser.php",
"line": 130,
"message": "Fluid parse error in template php:\/\/stdin, line 2 at character 27. Error: Variable identifiers cannot start with a \"_\": _invalidVariable (error code 1765900762). Template source chunk: {_invalidVariable}\n",
"templateLocation": {
"identifierOrPath": "php:\/\/stdin",
"line": 2,
"character": 27
}
}
],
"deprecations": [
{
"file": "\/var\/www\/html\/typo3\/sysext\/form\/Classes\/ViewHelpers\/Form\/TimePickerViewHelper.php",
"line": 143,
"message": "The TimePickerViewHelper is deprecated since TYPO3 v14 and will be removed in v15."
}
]
}
Copied!
Deprecating ViewHelpers
The
fluid:analyze
console command can catch deprecations of whole
ViewHelpers if the deprecation is emitted during the parse time of a template.
This is possible by implementing the
ViewHelperNodeInitializedEventInterface
:
ObsoleteViewHelper.php
useTYPO3Fluid\Fluid\Core\Parser\ParsingState;
useTYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
useTYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
useTYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface;
/**
* @deprecated since X, will be removed in Y.
*/finalclassObsoleteViewHelperextendsAbstractViewHelperimplementsViewHelperNodeInitializedEventInterface{
// ...publicstaticfunctionnodeInitializedEvent(ViewHelperNode $node, array $arguments, ParsingState $parsingState): void{
trigger_error(
'<my:obsolete> has been deprecated in X and will be removed in Y.',
E_USER_DEPRECATED,
);
}
}
Copied!
Deprecating ViewHelper arguments
The
ViewHelperNodeInitializedEventInterface
can be used to deprecate a ViewHelper argument. The deprecation is only
triggered if the argument is actually used in a template.
SomeViewHelper.php
useTYPO3Fluid\Fluid\Core\Parser\ParsingState;
useTYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
useTYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
useTYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface;
finalclassSomeViewHelperextendsAbstractViewHelperimplementsViewHelperNodeInitializedEventInterface{
publicfunctioninitializeArguments(): void{
// @deprecated since X, will be removed in Y.$this->registerArgument('obsoleteArgument', 'string', 'Original description. Deprecated since X, will be removed in Y');
}
publicstaticfunctionnodeInitializedEvent(ViewHelperNode $node, array $arguments, ParsingState $parsingState): void{
if (array_key_exists('obsoleteArgument', $arguments)) {
trigger_error(
'ViewHelper argument "obsoleteArgument" in <my:some> is deprecated since X and will be removed in Y.',
E_USER_DEPRECATED,
);
}
}
}
Copied!
Impact
The new
typo3 fluid:analyze
console command can be used to check basic
validity of Fluid templates in projects that use the *.fluid.* file
extension and to discover deprecated functionality in template files.
Feature: #108776 - Allow to set user interface language when using CLI to create user
The TYPO3 backend bookmark system has been comprehensively overhauled,
introducing a centralized architecture that replaces the legacy "shortcut"
implementation.
Bookmark groups
Bookmarks can be organized into three types of groups.
System groups are defined via user TSconfig using
options.bookmarkGroups
and are available to all users. Global
groups contain bookmarks visible to all backend users, though only
administrators can add bookmarks to these groups. User groups are custom groups
created by individual users for their own personal organization and are stored in
a new database table
sys_be_shortcuts_group
.
Five default bookmark groups are provided out of the box: Pages, Records,
Files, Tools, and Miscellaneous. Previously these groups were hardcoded in
PHP, but they are now defined via user TSconfig in EXT:backend, making them
fully customizable. The functionality remains the same, but administrators now
have complete control over which groups are available.
The
options.bookmarkGroups
setting should only be modified in
the global scope and not on a per-user basis, as inconsistent group
configuration between users can lead to unexpected behavior:
EXT:my_ext/Configuration/user.tsconfig
# Remove a specific default group (for example, Files)
options.bookmarkGroups.3 >
# Remove all default groups
options.bookmarkGroups >
# Add a custom group with a static label
options.bookmarkGroups.10 = My Custom Group
# Add a custom group with a translatable label using domain syntax
options.bookmarkGroups.11 = my_extension.messages:bookmark_group.custom
# Disable bookmarks entirely
options.enableBookmarks = 0
Copied!
Group labels support the TYPO3 translation domain syntax, allowing extensions
to provide translated group names. The format is
extension_key.messages:translation_key
, which resolves to the
default language file at
EXT:extension_key/Resources/Private/Language/locallang.xlf.
As before, group ID
-100
has special behavior as it is a superglobal
group. Bookmarks assigned to this group are visible to all backend users, but
only administrators can add or modify bookmarks in this group. This allows
administrators to provide a shared set of bookmarks across an entire TYPO3
installation.
Bookmark Manager
A new modal-based Bookmark Manager provides a centralized
interface for managing bookmarks. The manager supports drag-and-drop
reordering to reorganize bookmarks within and across groups. Bulk operations
allow selecting multiple bookmarks to move or delete at once. Users can
create, edit, and delete custom bookmark groups through the group management
interface, and rename bookmarks via inline editing.
The Bookmark Manager can be accessed via the bookmark icon in the
toolbar dropdown menu.
Impact
The bookmark toolbar item now opens a dropdown with quick access to recent
bookmarks and a link to the full Bookmark Manager. Users can
create custom bookmark groups to better organize their saved pages,
records, and modules. Administrators can configure global bookmarks visible
to all users. The Bookmarks dashboard widget has been updated to support the
new bookmark system with group filtering and limit options. The legacy
"shortcut" terminology has been replaced with "bookmark" throughout the
backend interface and code base.
Feature: #108799 - LocalizationRepository methods for fetching record translations
TYPO3 has historically provided helper methods for localization in various
places in the TYPO3 code. This patch centralizes localization-related functionality by marking
\TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository
as public (non-internal) and adding new methods as modern, DI-friendly
alternatives to the static
BackendUtility
methods.
getRecordTranslation()
Fetches a single translated version of a record for a specific language.
Fetches all translations of a record. This method can also be used to count
translations by using
count()
on the result, replacing the need for
BackendUtility::translationCount()
.
Returns an array of translated
RawRecord
objects indexed by language ID.
getPageTranslations()
Fetches all page translations for a page.
publicfunctiongetPageTranslations(
int $pageUid,
array $limitToLanguageIds = [],
int $workspaceId = 0,
bool $includeDeletedRecords = false,
): array
Copied!
Returns an array of page translation records as
RawRecord
objects indexed by language ID.
Impact
Extension developers working with record translations in the TYPO3 backend now
have access to modern, injectable repository methods that follow current TYPO3
coding practices.
The legacy static methods
BackendUtility::getRecordLocalization()
,
BackendUtility::getExistingPageTranslations()
, and
BackendUtility::translationCount()
remain available for backward
compatibility until they are migrated completely.
Feature: #108815 - CLI commands for system configuration
New CLI commands have been introduced to manage TYPO3 system configuration
(stored in config/system/settings.php) directly from the command line.
The following commands are now available:
configuration:show
Shows a configuration value. By default, if the active value differs from the
local value (for example, due to overrides in
config/system/additional.php), both values are displayed with the
difference highlighted.
# Show configuration (with diff if overridden)
vendor/bin/typo3 configuration:show SYS/sitename
# Show active (effective runtime) value
vendor/bin/typo3 configuration:show SYS/sitename --type=active
# Show local (settings.php) value only
vendor/bin/typo3 configuration:show DB/Connections/Default --type=local# Output as JSON
vendor/bin/typo3 configuration:show BE/debug --type=active --json
Copied!
configuration:set
Sets a configuration value in config/system/settings.php.
# Set a string value
vendor/bin/typo3 configuration:set SYS/sitename "My Site"# Set boolean or integer values using --json
vendor/bin/typo3 configuration:set BE/debug true --json
vendor/bin/typo3 configuration:set SYS/displayErrors 1 --json
# Set an array value
vendor/bin/typo3 configuration:set EXTENSIONS/my_extension '{"key": "value"}' --json
Copied!
The
--json
option parses the value as JSON, which allows
booleans, integers, and arrays to be set with proper types.
configuration:remove
Removes configuration value or values from config/system/settings.php.
# Remove a single path (asks for confirmation)
vendor/bin/typo3 configuration:remove EXTENSIONS/my_extension/setting
# Remove without confirmation
vendor/bin/typo3 configuration:remove EXTENSIONS/my_extension/setting --force
# Remove multiple paths (comma-separated)
vendor/bin/typo3 configuration:remove "EXTCONF/ext1,EXTCONF/ext2" --force
Copied!
Impact
These commands provide a convenient way to manage TYPO3 system configuration
from the command line, which is especially useful for:
automated deployment and provisioning scripts
CI/CD pipelines that need to adjust configuration
quick configuration changes without needing to access the Install Tool
scripting and automation tasks
The commands respect TYPO3 configuration path restrictions and only allow
writing to paths that are defined in the default configuration or explicitly
allowed (such as
EXTENSIONS
,
EXTCONF
,
DB
).
Feature: #108817 - Introduce web component-based form editor tree
The Form Editor tree component has been completely modernized. It has been migrated from
a legacy jQuery-based implementation to a modern web component architecture
using Lit and the TYPO3 backend tree infrastructure.
Enhanced user experience
The new tree component provides a significantly improved user experience with
modern interaction patterns and visual feedback:
Intuitive drag and drop
Form elements can now be reorganized using a smooth drag-and-drop interface
with intelligent validation rules. The tree automatically prevents invalid
operations, such as dragging the root form element, moving pages outside
their designated level, or dropping elements into non-composite types.
Smart element organization
Only composite elements like grid containers and fieldsets can receive
child elements, while simple form fields remain non-droppable. Pages must
always stay at the top level, ensuring proper form structure. The tree
automatically distinguishes between reordering siblings and changing parent
elements, providing precise control over form organization.
Visual feedback
Clear visual indicators show valid drop zones during drag operations.
Selected elements are highlighted with proper styling. The tree provides
immediate feedback for all interactions, making form building more
intuitive.
Persistent navigation
The tree remembers expanded and collapsed states. After drag
and drop operations, the tree maintains your current view and selection,
preventing disorientating resets. Navigation feels natural and responsive.
Integrated search
A built-in search toolbar allows quick filtering of form elements by name.
The search works client-side for instant results, making it easy to locate
specific elements in complex forms.
Collapse-all functionality
The toolbar includes a convenient button to collapse all expanded nodes at
once, helping to get a quick overview of your form structure or reset the
view to a clean state.
Technical implementation
The new implementation leverages the proven TYPO3 backend tree infrastructure.
Impact
Form editors will immediately notice the improved responsiveness and modern
feel of the tree component. Drag-and-drop operations are smoother and more
predictable. The search functionality makes working with large forms
significantly easier. The tree maintains its state during operations, reducing
friction and improving workflow efficiency.
The new web component-based architecture ensures better maintainability and
extensibility for future enhancements. The component integrates seamlessly with
the existing Form Editor without requiring changes to form definitions or
configuration.
Feature: #108819 - RecordFieldPreviewProcessor for custom PreviewRenderers
A new service
\TYPO3\CMS\Backend\Preview\RecordFieldPreviewProcessor
has been introduced to provide common field rendering helpers for custom
content element preview renderers.
Previously, these helper methods were only available in
StandardContentPreviewRenderer
,
which required custom preview renderers to extend that class to access them.
Instead, this service uses the composition-over-inheritance pattern.
The new service provides the following methods:
prepareFieldWithLabel()
Renders a field value with its TCA label prepended in bold.
Extension developers implementing custom preview renderers can now inject
RecordFieldPreviewProcessor
to access common field rendering helpers without extending
StandardContentPreviewRenderer
.
A new backend module Link Management > Short URLs has been introduced.
It enables editors to create and manage short URLs that redirect visitors to a
configurable target. Short URLs are stored as
sys_redirect
records with a
dedicated record type
short_url
, providing a streamlined editing form that
hides redirect-specific fields that are irrelevant to short URL use cases.
Creating short URLs
Short URLs can be created in two ways:
Manual entry: Editors type a custom path (for example, /promo) into the
source path field.
Auto-generation: Clicking the Generate Short URL button
generates a random 8-character path (for example, /aBcDeFgH). The
path is guaranteed to be unique due to server-side collision checking.
Uniqueness enforcement
Short URL paths must be unique to each source host. Duplicate detection happens at
two levels:
Client-side validation: While editing, the source path and source host
fields are validated against existing records. If a conflict is detected,
both fields are highlighted with an error state together with a notification.
Server-side enforcement: On save, the
DataHandler
rejects duplicate short
URLs and displays a flash message, ensuring data integrity even if
client-side validation is bypassed.
Immutability
Once a short URL record has been saved, the source path and source host fields
become read-only. This ensures that published short URLs remain stable and
previously shared links continue to work. The redirect target can be
changed at any time.
Clipboard support
The full short URL, including protocol and host, can be copied to the clipboard
from both the list overview and the record editing form.
Impact
Editors benefit from a dedicated interface for managing short URLs without
needing to understand redirect configuration details. The module provides a
central location for creating, reviewing, and maintaining short URLs with
built-in safeguards against duplicates and accidental modifications.
Feature: #108831 - Extend workspace preview links to all records
The Workspaces module previously generated shareable preview links
(with ADMCMD_prev token) for pages only. This change extends the functionality
to support any record type.
The preview page for non-page records is determined via existing user TSconfig
options:
options.workspaces.previewPageId.<table>
TCEMAIN.preview.<table>.previewPageId
Additional query parameters can be configured via:
The QR code and shareable link button in the Workspaces module now
work for all record types that have a preview configuration. This allows editors
to share workspace previews of custom records with colleagues or clients without
requiring them to have a backend login.
Feature: #108832 - Introduce UserSettings object for backend user profile settings
A new
\TYPO3\CMS\Core\Authentication\UserSettings
object provides structured access to backend user
profile settings defined in
$GLOBALS['TYPO3_USER_SETTINGS']
.
UserSettings object
The
UserSettings
object can be retrieved via the backend user:
/** @var \TYPO3\CMS\Core\Authentication\UserSettings $userSettings */
$userSettings = $GLOBALS['BE_USER']->getUserSettings();
// Check if a setting existsif ($userSettings->has('colorScheme')) {
$scheme = $userSettings->get('colorScheme');
}
// Get all settings as an array
$allSettings = $userSettings->toArray();
// Typed access via dedicated methods
$emailOnLogin = $userSettings->isEmailMeAtLoginEnabled();
$showUploadFields = $userSettings->isUploadFieldsInTopOfEBEnabled();
Copied!
The class implements
ContainerInterface
with
has()
and
get()
methods. The
get()
method throws
UserSettingsNotFoundException
if the setting does not exist.
New JSON storage with backward compatibility
Profile settings are now stored in a new
be_users.user_settings
JSON
field, providing a structured and queryable format. For backward compatibility,
the existing serialized
uc
blob continues to be written alongside:
// Writing still uses the uc mechanism
$GLOBALS['BE_USER']->uc['colorScheme'] = 'dark';
$GLOBALS['BE_USER']->writeUC();
// Both uc (serialized) and user_settings (JSON) are updated
Copied!
An upgrade wizard, "Migrate user profile settings to JSON format", migrates
existing settings from the
uc
blob to the new
user_settings
field.
Impact
Backend user profile settings can now be accessed via the
UserSettings
object, providing
type safety and IDE support. The new JSON storage format improves data
accessibility while maintaining full backward compatibility through dual-write
to both storage formats.
Feature: #108842 - Add badge for slide mode in Layout module
This feature introduces a visual badge in the Content > Layout module to
indicate when slide mode is active. The badge is a clear indicator for
editors, enhancing the user experience by providing immediate feedback on the
current mode of operation.
Each slide mode has a corresponding badge and description text:
For
slideMode = none
, no badge is shown.
For
slideMode = slide
, a badge with the text "Slide" is shown, but only
if there are no content elements on the current page.
For
slideMode = collect
, a badge with the text "Collect" is shown.
For
slideMode = collectReverse
, a badge with the text "CollectReverse"
is shown.
Feature: #108843 - User settings configuration migrated to TCA
The backend user profile settings configuration that was previously stored in
$GLOBALS['TYPO3_USER_SETTINGS']
is now available in TCA at
$GLOBALS['TCA']['be_users']['columns']['user_settings']
.
This allows user settings to benefit from TCA-based tooling and provides
a consistent API that extensions already use for other configuration.
A new method
ExtensionManagementUtility::addUserSetting()
has been introduced to
simplify adding custom fields to user profile settings.
Impact
Extensions can add custom fields to backend user profile settings using
the new
addUserSetting()
method in
Configuration/TCA/Overrides/be_users.php:
The
user_settings
TCA column has the following structure:
columns
Array of field configurations, each containing:
label
The field label (LLL reference or string)
config
Standard TCA config array (type, renderType, items, etc.)
table
(optional)
Set to
'be_users'
if the field is stored in a
be_users
table column
showitem
Comma-separated list of fields to display; supports
--div--;
for tabs
Available field types
input
- Text input field
number
- Number input field
email
- Email input field
password
- Password input field
check
with
renderType => 'checkboxToggle'
-
Checkbox or toggle
select
with
renderType => 'selectSingle'
-
Select field
language
- Language selector
Backward compatibility
For backward compatibility, the legacy
$GLOBALS['TYPO3_USER_SETTINGS']
array is still supported. Third-party
additions are automatically migrated to TCA after all
ext_tables.php files have been loaded. However, this approach is
deprecated, and extensions should migrate to the new TCA-based API.
Feature: #108846 - Console command to inspect global ViewHelper namespaces
The new console command typo3 fluid:namespaces has been introduced. It
lists all the available global ViewHelper namespaces in the current project and
can be used to verify the current configuration. The --json option
can be used to access the information in a machine-readable way.
A new
<f:render.text>
ViewHelper has been added. It provides a consistent
approach for outputting field values in templates where the field is part of a
record.
The ViewHelper follows the same conventions as other rendering-related
ViewHelpers and can be used wherever a text-based database field needs to be
displayed in the frontend.
The ViewHelper is record-aware. It receives the full record and field name,
and renders the field according to the field's TCA configuration. This includes
both plain text and rich text fields.
By default, accessing a field that is not available in a record
raises an exception. In order to support shared templates that need to be rendered even
if a field is missing, the optional boolean argument
optional
can be set
to
true
. The ViewHelper will then return
null
instead.
The input can be a
RecordInterface
,
PageInformation
, or a
DomainObjectInterface
.
This means records, ContentBlockData objects, PageInformation
objects, and Extbase models can be input. PageInformation objects and Extbase models are
converted internally to a RecordInterface.
Usage
Usage with the
record-transformation
data processor:
dataProcessing {
10 = record-transformation
}
Copied!
Based on the field's TCA configuration of the record in question, the ViewHelper
chooses the appropriate processing of the field (plain text, multiline text, or
rich text) without needing further configuration in the template.
MyContentElement.fluid.html
<f:render.textrecord="{record}"field="title" />
or
<f:render.textfield="title">{record}</f:render.text>
or
{f:render.text(record: record, field: 'title')}
or
{record -> f:render.text(field: 'title')}
This is useful for shared partials, for example in
fluid_styled_content
. A header partial can be reused by content
elements whose transformed record does not provide a
header
or
subheader
field. Without
optional="true"
, rendering such a
partial would raise a
RecordPropertyNotFoundException
. With
optional="true"
, the ViewHelper returns
null
and the partial can
continue to handle the missing value gracefully.
Usage with an Extbase model (property name differs from database field name):
The
field
argument always refers to the database or TCA column name of
the underlying record, even if your Extbase model maps that column to a
differently named property.
Note that Extbase models need to contain all columns to be rendered and
the record type column (if configured in TCA) for this to work correctly. For
example, an Extbase model that represents tt_content must map both bodytext
and CType to be able to use
<f:render.text record="{contentModel}" field="bodytext" />
.
Blog/Templates/Post/Show.fluid.html
<f:render.textrecord="{post}"field="short_description" /><!-- Example: Post->shortDescription maps to DB field "short_description";
use field="short_description" here. -->
Copied!
Previously, you needed to choose different processing for plain text and rich
text fields. You can now use the same ViewHelper for all field types.
For reference, similar results could previously be achieved using:
MyContentElement.fluid.html
{record.title}
Copied!
or multiline text:
MyContentElement.fluid.html
<f:format.nl2br>{record.description}</f:format.nl2br>
or
{record.description -> f:format.nl2br()}
Copied!
or, for rich text:
MyContentElement.fluid.html
<f:format.html>{record.bodytext}</f:format.html>
or
{record.bodytext -> f:format.html()}
Copied!
Migration
Extensions that previously accessed field values with
{record.title}
can continue to do so. However, using
<f:render.text>
is recommended instead because it renders the field in the
context of the record and applies processing based on the field configuration.
When migrating from formatting ViewHelpers like
<f:format.nl2br>
or
<f:format.html>
to
<f:render.text>
, the main difference is that
the new ViewHelper is aware of the record it belongs to and renders the field
based on the record's TCA schema.
If a template intentionally accesses fields that might not be available in every
record, for example shared
fluid_styled_content
header partials
used by custom content elements that do not have a visible
header
field, use the
optional
argument to preserve the previous behavior of treating the
missing field as empty output.
Impact
Theme creators are encouraged to use the
<f:render.text>
ViewHelper for
rendering text-based fields (plain text and rich text) as it provides a
standardized, record-aware approach that can be built upon in future versions.
The ViewHelper takes both the record and the field name as arguments so the
rendering process has access to the complete record context. This makes the
ViewHelper more flexible than directly accessing the field value.
Feature: #108904 - Add generic error action for custom HTTP status codes in ErrorController
The
\TYPO3\CMS\Frontend\Controller\ErrorController
has been enhanced with
a new method
customErrorAction()
which allows custom error handling
for HTTP status codes.
The new method can be used with TYPO3 site error handling, allowing site
administrators to configure dedicated error handling (for example, rendering a
Fluid template) for a given status code.
It is now possible to trigger custom error pages with specific HTTP status
codes and messages from within TYPO3 or extensions, while still respecting
the error handling in the main site configuration.
The TYPO3 backend now features a new guided "Page Creation Wizard" designed to
streamline the page creation process. This interface replaces the traditional,
technically complex workflow with a modular and accessible step-by-step
process.
The primary goals of the wizard are to ensure data integrity by enforcing
mandatory fields during creation which improves accessibility, and provides a modern,
responsive user experience that does not require deep TYPO3-specific expertise.
Key features
Guided workflow: A step-by-step process including positioning, type
selection, data entry, and a final review before persistence.
Data integrity: Validation of required fields (for example, page title)
occurs at each step to prevent broken or incomplete page records.
Context-aware: The wizard can be triggered from various entry points
(for example, page tree, Content > Records module) and respects
predefined parameters like position and page type.
Modular and extensible: Built using a generic architecture that allows
integrators to add custom steps or extend existing configuration for
specific page types.
FormEngine integration: Dynamic steps are rendered using FormEngine,
ensuring that all TCA-based rules and field configurations are respected.
Post-creation actions: After successful creation, users can choose whether to
jump to the Content > Layout module, create another page,
or return to their previous task.
Impact
Editors benefit from a faster, less error-prone way to build page structures.
The intuitive interface significantly lowers the barrier to entry for new users
while maintaining the flexibility required by power users.
Developers and integrators can leverage the modular design to customize the
creation process for custom doktype values or even adapt the wizard concept
for other TYPO3 workflows in the future.
Feature: #108941 - Provide language labels as virtual JavaScript modules
JavaScript modules can now import language labels as code.
The labels are exposed as an object with a get() method
and allows placeholder substitution conforming to the ICU message format.
// Import labels from language domain "core.bookmarks"import { html } from'lit';
import labels from'~labels/core.bookmarks';
// Use label
html`<p>{labels.get('groupType.global')}</p>`// Retrieve label and use ICU MessageFormat placeholders// Example label: <source>File "{filename}" deleted</source>
html`<p>{labels.get('file.deleted', { filename: 'my-file.txt' })}</p>`// Render a label containing pseudo XML tags// Example label: "File <bold>{filename}</bold> deleted"
html`<p>{labels.get('file.deleted', {
filename: 'my-file.txt',
// Callback function that renders the contents of <bold>
bold: chunks => html`<strong>${chunks}</strong>`,
})}</p>`
Copied!
This means controllers do not need to inject arbitrary labels into the
global TYPO3.lang configuration, which impeded writing generic web
components.
Virtual JavaScript modules (schema ~labels/{language.domain})
are created that resolve the labels for the specified language domain
provided after the ~labels/ prefix. This mapping is
implemented technically by using an import map path prefix which instructs the
JavaScript engine to append a specified suffix to the mapped prefix.
The labels can be cached client-side with a far-future cache lifetime,
similar to static resources. TYPO3 therefore generates version-specific
and locale-specific URLs to ensure labels can be cached by the user
agent without requiring explicit cache invalidation.
Impact
Extension developers can now use labels in JavaScript components without
requiring labels to be preloaded globally or per module, reducing the
risk of missing labels and also simplifying developer workflows.
Workarounds such as pushing labels to the top frame,
loading labels globally, and adding labels to component attributes
have previously been used and are replaced by this infrastructure.
Feature: #108966 - Rich text editor support in TYPO3 form editor
The TYPO3 form editor now supports rich text editing for textarea fields with
CKEditor 5. Form elements can be configured to use any available RTE preset,
which provides a consistent editing experience across the TYPO3 backend.
The implementation includes a new
\TYPO3\CMS\Form\Service\RichTextConfigurationService
that resolves
CKEditor configuration from global TYPO3 RTE presets and prepares it for use
in the form editor context. External plugins, such as the TYPO3 link browser,
are configured automatically.
Impact
Form integrators can now enable rich text editing in any textarea field in
the form editor by configuring it in the form YAML configuration.
The following form elements and finishers now support rich text editing out of
the box:
StaticText element - formatted text in forms
Checkbox element - labels with links for privacy policies, etc.
form-content - extended formatting for content fields (includes lists)
default - standard TYPO3 RTE with all features
minimal - minimal feature set
New form RTE presets
Two new RTE presets specifically designed for the form extension are now
available:
form-label
Essential formatting options for labels and short text fields.
Includes: bold, italic, link
form-content
Extended formatting options for content fields like StaticText.
Includes: bold, italic, link, bulleted lists, numbered lists
Configuration options
The following options are available for textarea editors in the form editor:
enableRichtext
Data type
boolean
Default
false
Description
Enables rich text editing for this textarea field.
richtextConfiguration
Data type
string
Default
form-label
Description
Name of the RTE preset to use. The preset must be registered in
$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']
.
Common presets: form-label, form-content, default, minimal,
full
Custom sanitizer configuration
The form extension uses a multi-layer sanitization approach for security:
Backend: Content is sanitized using the htmlSanitize.build setting
from the RTE preset processing configuration.
Frontend: Content is sanitized again using the default sanitizer via
the f:sanitize.html() ViewHelper.
To use a custom sanitizer in the backend, configure it in your RTE preset:
The frontend templates use f:sanitize.html() with the default sanitizer for
defense-in-depth security. To customize the frontend sanitization, integrators
have two options:
Option 1: Override Fluid templates
Override the form element templates and specify a custom sanitizer build:
Extbase class configuration (persistence mapping) is now exposed in the
backend System > Configuration module. The module is available
if the system extension
typo3/cms-lowlevel
is installed.
The displayed configuration reflects the configured mapping that Extbase uses
at runtime. It is built by collecting and merging all
EXT:my_extension/Configuration/Extbase/Persistence/Classes.php
definitions from active packages.
This is a read-only usability improvement. Developers and integrators can
inspect and verify resolved Extbase persistence class mapping such as
extension overrides in the backend, without having to dump configuration
arrays or manually check each
EXT:my_extension/Configuration/Extbase/Persistence/Classes.php file.
Feature: #108982 - Introduce rate limiting for Extbase actions
Extbase now supports rate limiting for controller actions using the new PHP
attribute
\TYPO3\CMS\Extbase\Attribute\RateLimit
. This feature allows
developers to restrict the number of requests a user can make to a specific
action within a given time frame.
Note
Rate limiting only works for uncached Extbase actions. For cached actions,
the TYPO3 frontend cache might return the response before the Extbase
controller is invoked, thus bypassing the rate limiting logic.
Rate limiting is based on the client's IP address and uses Symfony's
RateLimiter component with caching framework storage.
The
#[RateLimit]
attribute supports the following properties:
limit
: The maximum number of requests allowed (default: 5).
interval
: The time window for the limit (for example,
15 minutes, 1 hour) (default: 15 minutes).
policy
: The rate limiting policy to use (for example,
sliding_window, fixed_window) (default: sliding_window).
message
: An optional, localizable translation key for the error
message shown when the limit is reached, for example
messages.rate_limit_message (the translation domain, such as
my_extension, is added automatically and must not be part of the key),
or
LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:rate_limit_message.
When a rate limit is exceeded, Extbase returns a response with HTTP status code
429 by default (Too Many Requests).
Usage
Apply a rate limit to an Extbase action by adding a
#[RateLimit]
attribute
to the action method:
usePsr\Http\Message\ResponseInterface;
useTYPO3\CMS\Extbase\Attribute\RateLimit;
useTYPO3\CMS\Extbase\Mvc\Controller\ActionController;
classMyControllerextendsActionController{
#[RateLimit(limit: 3, interval: '1 minute', message: 'message.ratelimitexceeded')]publicfunctioncreateAction(): ResponseInterface{
// Business logic for creating an entityreturn$this->redirect('index');
}
}
Copied!
PSR-14 event: BeforeActionRateLimitResponseEvent
The new PSR-14 event
\TYPO3\CMS\Extbase\Event\BeforeActionRateLimitResponseEvent
is dispatched when a rate limit is triggered but before the response is
returned. This allows extension developers to modify the response or perform
additional actions, such as logging, throwing a custom exception, and enqueuing
a flash message.
The following example implementation shows how to throw a custom error if a rate
limit is reached. It is handled by a configured site error handler.
Developers can now protect sensitive Extbase actions (for example, form
submissions, login attempts, and heavy API endpoints) from abuse, spam, or
brute-force attacks with minimal effort.
Feature: #108992 - New PSR-14 event for workspace dependency resolution
A new PSR-14 event
\TYPO3\CMS\Workspaces\Event\IsReferenceConsideredForDependencyEvent
has been added. It is dispatched for each
sys_refindex
row when the
workspace dependency resolver evaluates which references constitute structural
dependencies during publish, stage, discard, and display operations.
Listeners decide whether a particular reference should be treated as a workspace
dependency. References are opt-in: the default is "not a dependency", and
listeners must explicitly mark relevant references.
The event has the following methods:
getTableName()
: The table owning the field
(
sys_refindex.tablename
).
getRecordId()
: The record owning the field
(
sys_refindex.recuid
).
getFieldName()
: The TCA field name (
sys_refindex.field
).
getReferenceTable()
: The referenced table
(
sys_refindex.ref_table
).
getReferenceId()
: The referenced record ID
(
sys_refindex.ref_uid
).
getAction()
: The
\TYPO3\CMS\Workspaces\Dependency\DependencyCollectionAction
enum
value (
Publish
,
StageChange
,
Discard
, or
Display
).
getWorkspaceId()
: The current workspace ID.
isDependency()
/
setDependency()
: Read or change whether this
reference is a structural dependency.
TYPO3 Core registers a listener that marks
type=inline
,
type=file
(with
foreign_field
), and
type=flex
fields as
dependencies.
A new enum
\TYPO3\CMS\Workspaces\Dependency\DependencyCollectionAction
has been added to represent the action context.
Example
A third-party extension that stores parent-child relationships in a custom
field can register a listener to include those references as workspace
dependencies:
Extensions can now register custom parent-child relationships as workspace
dependencies via this PSR-14 event. This ensures that structurally dependent
records are published, staged, or discarded together, preventing orphaned
records in workspaces.
The internal pseudo-event mechanism (EventCallback,
ElementEntityProcessor) that was previously used has been removed. This is an
internal change that does not affect the public API.
Feature: #109018 - PSR-14 event to modify indexed_search result sets
A new PSR-14 event
\TYPO3\CMS\IndexedSearch\Event\AfterSearchResultSetsAreGeneratedEvent
has been introduced to modify search result sets in
SearchController
.
The event is dispatched in
searchAction()
after all the result sets have
been built. Event listeners can manipulate complete result sets, including
pagination, rows, section data, and category metadata.
The event has the following methods:
getResultSets()
: Returns all the result sets from the current search.
setResultSets(array $resultSets)
: Replaces the result sets.
getSearchData()
: Returns the search configuration array.
getSearchWords()
: Returns an array of search words.
getView()
: Returns the view instance.
getRequest()
: Returns the current server request.
Example
The following example replaces every result set pagination with
SlidingWindowPagination
:
This event allows search result sets to be modified in a single listener
call. It enables custom pagination strategies, as well as advanced search
result transformations.
A new component has been added, based on the page-browser, that allows
a page to be selected and an insertion position to be defined. Possible
positions are inside and after.
Features
When a page node in the tree is selected insertion options are displayed.
Options include Insert and After. After is applicable to all
child pages.
On first render, the selected node is scrolled into view
using scrollNodeIntoViewIfNeeded.
The component emits a custom event
typo3:page-position-select-tree:insert-position-change whenever the
insertion position changes. The event payload contains pageUid (the
selected page ID) and position (the chosen insertion position),
allowing other modules to react accordingly.
This component can be used anywhere in the backend where page selection and
insertion position are needed, replacing previous workflows
with more intuitive controls.
Feature: #109080 - Unified RateLimiterFactory with admin overrides
TYPO3's
\TYPO3\CMS\Core\RateLimiter\RateLimiterFactory
has been
refactored to serve as the single entry point for rate limiting across the
system. A new
\TYPO3\CMS\Core\RateLimiter\RateLimiterFactoryInterface
extends Symfony's
RateLimiterFactoryInterface
with additional
convenience methods for request-based and login rate limiting.
Previously, backend and frontend password recovery features and
Extbase rate limiting each created Symfony rate limiter factories,
bypassing TYPO3's factory. All consumers now use the central TYPO3 factory,
which enables a unified admin override mechanism.
Extension developers should type-hint against
RateLimiterFactoryInterface
when injecting the factory.
Admin overrides via TYPO3_CONF_VARS
A new configuration option
$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']
allows administrators to override any rate limiter's settings by its ID. Each
key is a limiter ID, and each value is an array of settings to override:
The limiter ID for the action is
extbase-vendor-myextension-controller-mycontroller-dosomethingaction
General-purpose rate limiting
Extension developers can now use the factory for custom rate limiting needs.
The
createRequestBasedLimiter()
method is the recommended entry point
for request-scoped rate limiting. It extracts the client's
remote IP from the PSR-7 request and uses it as the limiter key:
In cases where a custom key is needed, for example a user ID instead of the
IP address, the
createLimiter()
method accepts an explicit
configuration array and key:
Preconfigured named services can also be defined in Services.yaml.
They are then injectable with the
create()
method from the
RateLimiterFactoryInterface
:
All rate limiting in TYPO3 now flows through a single factory that supports
admin-level overrides. Administrators can tune or restrict rate limits for any
component—login, password recovery, Extbase actions, or custom extensions—
without modifying code, using
$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']
.
The login rate limiter now uses human-readable IDs (login-be, login-fe)
instead of SHA1 hashes. Existing cached rate limit state from previous
versions will expire naturally.
Feature: #109087 - Introduce BeforeBackendPageRenderEvent for BackendController
A new PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\BeforeBackendPageRenderEvent
has been introduced. It is dispatched in
BackendController
before the main backend page is rendered. It provides access to:
$view
(
ViewInterface
) – assign template
variables to the backend top frame view
$javaScriptRenderer
(
JavaScriptRenderer
) – add
custom JavaScript modules to the backend top frame
$pageRenderer
(
PageRenderer
) – add assets
such as CSS files (marked
@internal
)
It is now possible to add custom JavaScript modules and other assets to the
TYPO3 backend top frame using the new PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\BeforeBackendPageRenderEvent
.
A new
priority
column has been added to the
tx_scheduler_task
table, allowing administrators to control the
execution order of scheduler tasks. Three levels are available:
High (150)
Regular (100, default)
Low (50)
The scheduler now selects the next executable task ordered by
priority DESC
first, using
nextexecution ASC
as a secondary
tiebreaker. This means a high-priority task is always
executed before a lower-priority task, regardless of how long the
lower-priority task has been waiting.
The priority field is exposed as a select field in the Timing tab of
the task editing form in all registered task types. The priority of each
task is also visible in the scheduler backend module list view.
Extending priority levels
Extensions can add custom priority levels by extending the TCA of
tx_scheduler_task
. The
priority
field is a plain integer
column, so any positive integer value is valid. The scheduler module
automatically resolves the label of any registered TCA item, so that custom
values are displayed correctly in the list view. The TCA item's
label
key must point to a valid language label. If no matching item
is found, the raw integer is shown.
Choose integer values that fit naturally into the existing scale (50 /
100 / 150). Values above 150 are executed before High, values below
50 after Low.
Impact
Administrators can now assign a priority to each scheduler task. Tasks
with High priority are picked up before Regular tasks, and
Regular before Low tasks. If multiple tasks share the same
priority, the longest-overdue task is still selected first, preserving
the previous behavior as a tiebreaker.
Existing tasks receive the default priority Regular (100)
automatically via the schema update — no data migration is required.
Feature: #109114 - Autocomplete for components via XSD schema
The existing CLI command
typo3 fluid:schema:generate
has been extended to cover
Fluid components. When executed, the command creates *.xsd files in
var/transient/ for all available ViewHelpers and components,
which can be used by IDEs for autocompletion.
Usage:
vendor/bin/typo3 fluid:schema:generate
Copied!
In order to work correctly the responsible component collection
needs to implement the new
ComponentListProviderInterface
.
TYPO3's Fluid components integration
already implements this, so these components are supported out of the box.
Fluid Standalone has a default implementation of custom component
collections that are based on
AbstractComponentCollection
,
which should cover components that were created before the official components
integration (such as those created with TYPO3 v13). However, if a custom
folder structure is used by overriding the default
resolveTemplateName()
, a custom implementation of
getAvailableComponents()
must be provided. In most cases, it
is easier to switch to the TYPO3 integration and remove the custom class.
Impact
The CLI command
typo3 fluid:schema:generate
now creates XSD
schema files for Fluid components, enabling autocompletion in supporting
IDEs.
Feature: #109126 - Introduce date editor for ext:form
A new web component
<typo3-form--date-editor>
has been
introduced in the form editor backend. It replaces the plain text
input in the
DateRange
validator minimum/maximum fields and the
defaultValue
field in the
Date
form element.
Previously editors had to manually type date values or relative expressions
like
-18 years
into a plain text field. The new structured editor
provides a user-friendly UI which has five modes:
No value - Clears the constraint (empty value)
Today - Sets the value to
today
Absolute date - A native HTML5 date picker that produces Y-m-d values
Relative date - Structured input with direction (past/future), amount
and unit (days, weeks, months, years), which produces expressions like
-18 years
or
+1 month
Custom relative expression - Free-text input for arbitrary relative
date expressions that go beyond the structured input, such as compound
expressions like
+1 month +3 days
. The input is validated against
the configured relative date pattern.
Impact
The form editor backend now provides a structured, user-friendly editor for
date constraints and default values in
Date
form elements. Editors no
longer need to know the PHP relative date syntax - they can simply select
a mode, direction, amount, and unit from dropdown fields. For advanced use
cases, the custom mode allows arbitrary relative date expressions
with real-time validation to be entered. Existing form definitions are not affected and
continue to work without change.
Feature: #109130 - Context-aware editing in the layout module
The Content > Layout module now features a context panel
for editing page properties and content elements. Clicking on an edit
button opens a slide-in panel next to the page layout. The editing form
is displayed inside the panel with the page layout remaining visible in
the background.
The panel supports all FormEngine fields in an improved UI. The panel
header displays the record title as well as Save and Close
buttons. An Expand button allows switching to the full record
editing form in the content area at any time. After saving, the panel
stays open for further edits.
User settings
The context panel is enabled by default. It can be disabled per user
in User Settings via the
Use quick editing for records in the page module option. When
disabled, edit buttons navigate to the full record editing form
as before. The setting takes effect immediately.
Impact
Editors can now edit records in the Content > Layout module
without leaving the page layout context. The full editing form remains
accessible for more complex editing tasks.
Feature: #109163 - Implement public system resources publishing
The most visible feature of this implementation is the new
asset:publish command. This command can publish public
extension resources from their Resources/Public folder to the document root
directory (public by default in Composer mode).
To maintain backward compatibility for Composer mode installations, this
command is automatically executed during composer install. This means
that after Composer has done its job installing packages, extension assets
are already published.
Public extension resources are also published when extensions are set up
with the extension:setup command or when an extension is activated in the Extension
Manager. Because of this, and because it might not be desirable or applicable
to publish assets at Composer build time, it is now possible to skip publishing
during composer install by setting an environment variable
TYPO3_SKIP_ASSET_PUBLISH, for example:
TYPO3_SKIP_ASSET_PUBLISH=1 composer install.
Not publishing assets at composer install is likely to become default behavior
in future TYPO3 versions.
TYPO3 ships file system-based publishing only. From now on, however, there is
an additional strategy available besides symlink publishing (*nix systems) and
junction publishing (Windows systems). TYPO3 can now copy all files and
folders from their private locations to the document root. This is useful for
many use cases such as container builds, deployments with read-only file
systems, restrictive hosting environments, and others.
By default, the linking strategy is being kept, particularly for backward
compatibility reasons. It is, however, possible to influence the behavior by
setting the following configuration option:
Although TYPO3 Core only delivers file system-based publishing, third-party
extensions can now implement other ways of publishing public system resources.
By implementing
\TYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface
and registering the implementing class as an alias of the interface, TYPO3
will use this not only to publish system resources, but also to generate URIs
that reflect their new location, for example on a CDN.
This also works in TYPO3 classic mode, because publishing is now part of
extension activation.
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Service;
usePsr\Http\Message\ServerRequestInterface;
usePsr\Http\Message\UriInterface;
useSymfony\Component\DependencyInjection\Attribute\AsAlias;
useSymfony\Component\DependencyInjection\Attribute\Autoconfigure;
useTYPO3\CMS\Core\Http\Uri;
useTYPO3\CMS\Core\Messaging\FlashMessageQueue;
useTYPO3\CMS\Core\Package\PackageInterface;
useTYPO3\CMS\Core\SystemResource\Publishing\DefaultSystemResourcePublisher;
useTYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface;
useTYPO3\CMS\Core\SystemResource\Publishing\UriGenerationOptions;
useTYPO3\CMS\Core\SystemResource\Type\PublicPackageFile;
useTYPO3\CMS\Core\SystemResource\Type\PublicResourceInterface;
#[Autoconfigure(public: true), AsAlias(SystemResourcePublisherInterface::class, public: true)]final readonly classExampleResourcePublisherimplementsSystemResourcePublisherInterface{
privateconst CDN_URL = 'https://my.awsome.cdn/files/';
publicfunction__construct(
private DefaultSystemResourcePublisher
$defaultSystemResourcePublisher,
){}
publicfunctionpublishResources(
PackageInterface $package,
): FlashMessageQueue{
// Additional logic to publish files to a CDN could be added// here. For this example, the CDN loads the assets from the// source automatically, so resources are published as usual.return$this->defaultSystemResourcePublisher->publishResources(
$package,
);
}
publicfunctiongenerateUri(
PublicResourceInterface $publicResource,
?ServerRequestInterface $request,
?UriGenerationOptions $options = null,
): UriInterface{
$defaultUri = $this->defaultSystemResourcePublisher->generateUri(
$publicResource,
$request,
new UriGenerationOptions(
uriPrefix: '',
absoluteUri: false,
cacheBusting: false,
),
);
if ($publicResource instanceof PublicPackageFile) {
returnnew Uri(self::CDN_URL . $defaultUri);
}
return $defaultUri;
}
}
Copied!
Impact
There is no apparent impact for any TYPO3 installation, as the changes are mostly internal
and the public API and behavior are the same as before. For deployments, nothing needs to
be changed, as asset publishing is still performed at composer install, and also
by the extension:setup command, both of which are already part of any deployment workflow.
Users, however, now have more control over when and how publishing is performed, by setting
the environment variable TYPO3_SKIP_ASSET_PUBLISH=1 for composer install or by configuring
the mirror strategy for publishing by setting
$GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] = 'mirror';
in config/system/additional.php.
Feature: #109167 - Improved exceptions in Fluid templates
In an effort to simplify debugging Fluid templates, TYPO3 14 enhances
exception messages thrown by Fluid in several ways:
Templates that contain invalid syntax or refer to undeclared ViewHelper
arguments now contain both the full path to the template file and the
affected line number in that file.
Most ViewHelper-related error messages now contain the full path to the
template file.
Fluid Standalone 5.2 (also backported to Fluid 4.6) introduces more granular
exception classes that can be used by ViewHelpers to classify runtime errors.
These classifications are also part of the error message.
When a referenced Fluid template cannot be found, the exception message
contains a full list of the candidates that have been tried. Also,
the exception contains the context in which the template file is missing
(for example FLUIDTEMPLATE or PAGEVIEW).
In order for this to work with custom ViewHelper implementations, ViewHelpers
need to use the base ViewHelper exception class or one of its child classes:
\TYPO3Fluid\Fluid\Core\ViewHelper\Exception
for general exceptions
\TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentException
for
general exceptions related to ViewHelper arguments
\TYPO3Fluid\Fluid\Core\ViewHelper\MissingArgumentException
if
a required ViewHelper argument has not been supplied
\TYPO3Fluid\Fluid\Core\ViewHelper\UndeclaredArgumentException
if
a ViewHelper is called with an argument that has not been defined
If any of these exception classes are used in a ViewHelper, Fluid's internal
error handler automatically adds the full path to the current template file
to the exception. It is not necessary for ViewHelpers to do this themselves.
Note that this leads to nested exceptions. The original exception can be
accessed via
$e->getPrevious()
.
Examples
Parse error in template
#1238169398 TYPO3Fluid\Fluid\Core\Parser\Exception
Fluid parse error in template /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html, line 11 at character 15.
Error: Not all tags were closed! (error code 1238169398). Template source chunk: test
Copied!
Undeclared ViewHelper argument
#1773227091 TYPO3Fluid\Fluid\Core\ViewHelper\Exception
TYPO3Fluid\Fluid\Core\ViewHelper\UndeclaredArgumentException in /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html:
Undeclared arguments passed to ViewHelper TYPO3Fluid\Fluid\ViewHelpers\Format\TrimViewHelper: foo. Valid arguments are: value, characters, side
(/var/www/html/vendor/typo3fluid/fluid/src/Core/ViewHelper/AbstractViewHelper.php:314)
Copied!
Custom validation by ViewHelper implementation
#1669191560 TYPO3Fluid\Fluid\Core\ViewHelper\Exception
TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException in /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html:
The side "none" supplied to Fluid's format.trim ViewHelper is not supported.
(/var/www/html/vendor/typo3fluid/fluid/src/ViewHelpers/Format/TrimViewHelper.php:118)
Copied!
Missing template file for PAGEVIEW
#1742058289 TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException
PAGEVIEW TypoScript object: Failed to resolve a template file for page layout "default". See also: https://docs.typo3.org/permalink/t3tsref:cobj-pageview@14.2.
The following paths were checked:
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default"
Copied!
Impact
To make debugging easier, exceptions that originate from Fluid templates now
contain more context, such as the full path to the template file.
Feature: #109187 - Add integrity property to CSS includes
The TypoScript properties
includeCSS
and
includeCSSLibs
now support the
integrity
property for Subresource Integrity (SRI) checking, bringing CSS includes
to parity with existing SRI support in
includeJS
,
includeJSFooter
,
includeJSLibs
, and
includeJSFooterlibs
.
The
crossorigin
property is also supported. When
integrity
is set without an explicit
crossorigin
value for URI resources (for example, external
URLs),
crossorigin="anonymous"
is automatically added,
which is required for SRI validation to work cross-origin.
The
integrity
attribute has not yet been added to inline styles
(
inline = 1
), as SRI only applies to external
resources.
The
PageRenderer::addCssFile()
and
PageRenderer::addCssLibrary()
methods have gained two new
parameters,
$integrity
and
$crossorigin
.
Impact
It is now possible to add SRI integrity hashes to CSS files included via
TypoScript:
page.includeCSS {
main = https://cdn.example.com/styles/main.css
main.integrity = sha384-abc123==
# crossorigin is auto-set to "anonymous" when integrity is given
}
page.includeCSSLibs {
vendor = https://cdn.example.com/vendor.css
vendor.integrity = sha384-xyz789==
vendor.crossorigin = anonymous
}
The integrity hash value can be generated using browser
developer tools or command-line tools. See Subresource Integrity on
MDN
for details on how to generate and use integrity hashes.
Feature: #109187 - Automatic SRI hash resolution for resource includes
Setting
integrity = auto
on any resource include that supports
the
integrity
property causes TYPO3 to automatically compute and
inject the Subresource Integrity (SRI) hash for that resource instead of
requiring a manually precomputed hash value.
This works for all integrity-supporting TypoScript include properties:
page.includeCSS
page.includeCSSLibs
page.includeJS
page.includeJSLibs
page.includeJSFooter
page.includeJSFooterlibs
The hash is computed using SHA-256, and the result is cached via
cache.assets
with a 7-day TTL, so there is no per-request overhead
after the first render of a given resource.
For external URL resources,
crossorigin="anonymous"
is added
automatically after the hash is successfully resolved, as required by the SRI
specification for cross-origin resources.
Note
When using
integrity = auto
for remote HTTP(S) URLs, TYPO3
fetches the resource from the remote server to compute its hash. The
computed hash reflects the content returned by the remote server at the
time of the first fetch. The remote server must therefore be trusted.
If the remote resource is compromised or altered at the time of the initial
fetch, the hash will be computed from the compromised content, and browsers
will subsequently accept it. For maximum security, use explicit
precomputed hash values (e.g.
integrity = sha256-abc123==
)
obtained from a trusted source rather than relying on automatic resolution
for externally hosted resources.
The equivalent PHP constant
\TYPO3\CMS\Core\Page\ResourceHashCollection::AUTO
can be used when calling
PageRenderer
or
AssetCollector
APIs.
Impact
It is now possible to enable SRI for resource includes without manually
computing the hash value. Setting
integrity = auto
is sufficient:
page.includeCSS {
main = https://cdn.example.com/styles/main.css
main.integrity = auto
# crossorigin="anonymous" is added automatically for external URLs
}
page.includeJS {
app = EXT:my_extension/Resources/Public/JavaScript/app.js
app.integrity = auto
}
When using the PHP API directly, pass
ResourceHashCollection::AUTO
as the
$integrity
argument:
useTYPO3\CMS\Core\Page\AssetCollector;
useTYPO3\CMS\Core\Page\PageRenderer;
useTYPO3\CMS\Core\Page\ResourceHashCollection;
// Ignore all parameters but the last one ($integrity). They are defaults,// but must be specified because TYPO3 Core API does not provide stable// argument names, so using named arguments for regular methods is not supported.
$pageRenderer->addCssFile(
'EXT:my_extension/Resources/Public/Css/style.css',
'stylesheet',
'all',
'',
null,
false,
'',
null,
'|',
false,
[],
ResourceHashCollection::AUTO, // parameter $integrity
);
$pageRenderer->addJsFile(
'EXT:my_extension/Resources/Public/JavaScript/app.js',
'',
null,
false,
'',
null,
'|',
false,
ResourceHashCollection::AUTO, // parameter $integrity
);
$assetCollector->addStyleSheet(
'my-styles',
'EXT:my_extension/Resources/Public/Css/style.css',
[],
['integrity' => ResourceHashCollection::AUTO], // parameter $options
);
Copied!
Feature: #109214 - Native support for language Lao added
The
limitToPages
option for route enhancers in the site configuration
now supports Symfony Expression Language expressions in addition to plain
page IDs.
Previously,
limitToPages
accepted only an array of integer page IDs,
which required maintaining a static list that had to be updated whenever pages
were added or moved. This change means string entries in the
limitToPages
array are evaluated as Expression Language expressions,
giving integrators flexible, condition-based control over which pages a route
enhancer applies to.
All entries in the array are combined via logical OR. Integer values are
matched against the page ID (existing behavior), and string values are
evaluated as expressions. To combine multiple conditions with logical AND, use
the
&&
operator inside a single expression string.
Use the following variables inside expressions:
page
- The full page record as an associative array (for example,
page["doktype"]
,
page["backend_layout"]
,
page["module"]
)
site
- The current
Site
object
siteLanguage
- The current
SiteLanguage
object
All the default Expression Language functions such as
like()
,
env()
, and
feature()
are also available.
Extensions can register additional functions and variables for the
routing
Expression Language context via
Configuration/ExpressionLanguage.php.
Integrators can now use dynamic, expression-based conditions in
limitToPages
instead of maintaining static lists of page IDs. This is
fully backward compatible - existing configurations with integer-only arrays
continue to work without any changes.
The page creation wizard now supports the dynamic configuration of its steps
via TCA. This allows integrators to define which fields are displayed in
which step of the wizard, depending on the doktype.
A new TCA configuration option wizardSteps is introduced. It currently
works only for the pages table. Each step is defined by a unique key and
contains a title and a list of fields to be displayed.
The steps are sorted, allowing them to be positioned relative to each other
using the after or before keys.
If a page type has required fields that are not explicitly assigned to a
configured wizard step, a fallback step is automatically appended at the end.
Example
Defining wizard steps for a custom doktype in TCA:
The import/export module now supports the inclusion of site configurations that belong
to exported page trees. Admin users can enable this via the
Include site configurations for exported root pages checkbox in the
Advanced options tab of the export module. This option is only
available to admin users.
When enabled and a page tree belonging to a root page with a site
configuration is exported, that configuration is embedded in the export file. On import,
the site configuration is restored with the remapped root page UID.
During import, embedded site configurations are processed after all records
have been written:
The rootPageId is remapped to the newly imported page UID.
If a site configuration already exists for the imported root page, the
embedded configuration is skipped.
If the site identifier already exists but points to a different root page,
a numeric suffix is appended (e.g. my-site-1) to avoid collisions.
The import preview screen shows embedded site configurations with their
identifier, base URL, and the title of the associated root page.
The CLI export command supports the same feature using the
--include-site-configurations option.
Impact
Site configurations can now be preserved across export and import cycles. This
simplifies the distribution of complete site packages and the migration of page
trees between TYPO3 instances.
Feature: #109365 - Introduce module access gates for backend modules
The previously hard-coded module access checks (user, admin,
systemMaintainer) in the backend module registration have been replaced
with an extensible gate system. Each access type is now handled by a dedicated
gate class which implements
ModuleAccessGateInterface
.
TYPO3 ships three built-in gates that preserve existing behavior:
UserGate
- grants access
to admin users and users/groups with explicit module permissions
(
be_users.userMods
/
be_groups.groupMods
)
AdminGate
- grants access
only to admin users
SystemMaintainerGate
-
grants access only to system maintainers
Extension authors can register custom gates using the
#[AsModuleAccessGate]
PHP attribute. A gate receives the module and the
current backend user and returns one of three results:
ModuleAccessResult::Granted
- access is explicitly allowed
ModuleAccessResult::Denied
- access is explicitly denied
ModuleAccessResult::Abstain
- the gate cannot decide (not responsible
for this access type)
Extension authors can now define custom module access strategies beyond the
built-in user, admin, and systemMaintainer levels by implementing
ModuleAccessGateInterface
and
registering it with the
#[AsModuleAccessGate]
attribute.
Existing module registrations using the built-in access values continue to
work without changes.
Feature: #109366 - Hide form fields with no selectable items
Relational fields in the FormEngine, such as type=select with
foreign_table, type=category, and type=select, with
misconfigured empty items are now automatically handled when no selectable
items are available. Also, type=language fields are hidden if
only a single language is configured, since a dropdown with one choice serves
no purpose.
For regular users, the field is removed entirely. When backend debug mode is
enabled (
$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = true
), the field
is kept as read-only with an info badge so admins can identify configuration
issues such as missing records or restrictive TSconfig.
Common scenarios where this applies:
A categories field is displayed but no
sys_category
records exist.
A select field references
fe_groups
, but no frontend user groups
have been created.
All available items have been removed via TSconfig
removeItems
or
keepItems
configuration.
The backend user has no permissions for any of the available items.
A site has only one configured language.
A type=select field is misconfigured and has no items.
Affected field types
type=select (all render types including selectTree): hidden when no
items are available. For fields with foreign_table, static items like
"Hide at login" are not counted - they only make sense when actual foreign
records exist.
type=category: hidden when no items are available
type=language: hidden when only one language is available (the special
-1 "All languages" item is not counted as a meaningful choice)
Existing values
Fields not rendered in the form are simply not submitted on save. The DataHandler
preserves the existing database value for non-submitted fields, so no data is
lost when a field is hidden by this feature.
Opt-out
The behavior can be disabled per field using the TCA configuration option
showIfEmpty:
Fields without selectable items are now hidden by default. In backend debug
mode, the fields are shown as read-only with an info badge.
Extensions that rely on empty fields always being shown should set
showIfEmpty to true in their TCA configuration.
Feature: #109370 - Array-based queryParameters for page links
When page links are created programmatically by the
LinkFactory
PHP API,
query parameters previously had to be provided as a URL-encoded string in
the additionalParams configuration key. A new queryParameters
configuration key has been introduced that accepts a PHP array, including
multi-dimensional arrays.
When both queryParameters and additionalParams are set, they are
merged using
array_replace_recursive()
, with queryParameters
taking precedence.
The Fluid ViewHelpers <f:link.page>, <f:uri.page>,
<f:link.action>, and <f:uri.action> now use this option
internally to pass their additionalParams argument as an
array, eliminating the previous serialize/deserialize round trip via query
string encoding.
Impact
Developers creating page links via the
LinkFactory
PHP API can now pass
query parameters as structured arrays via the queryParameters
configuration key. This avoids manual query string encoding and makes
multi-dimensional parameter handling more natural.
The option can be combined with the existing string-based
additionalParams. When both are provided, queryParameters values
override matching keys from additionalParams.
Feature: #109409 - Allow configuration of resources
Until now, extensions could only place public resources in their
Resources/Public folder. The folder name used to publish these
extension resources was a non-configurable MD5 hash.
This feature introduces the possibility to configure resources explicitly.
This includes additional public folders or files that are published in
TYPO3's public/_assets folder, as well as non-public resource paths.
TYPO3 classic mode
In TYPO3 classic mode, there is no visible change for extensions
or the typo3/app package, since files in extensions are already
located within the document root. Restricting resources to the default
locations in classic mode therefore mainly follows coding guidelines
and keeps compatibility with Composer mode.
Configuring extensions
Extensions can add Configuration/Resources.php to configure resources.
This configuration is then added to the following default configuration:
<?phpdeclare(strict_types=1);
useTYPO3\CMS\Core\Package\Package;
useTYPO3\CMS\Core\Package\Resource\Definition\PublicResourceDefinition;
useTYPO3\CMS\Core\Package\Resource\Definition\ResourceDefinition;
returnstaticfunction(Package $package){
$resourceDefinitions = [
new ResourceDefinition('Resources/Private'),
];
if (is_dir($package->getPackagePath() . 'Resources/Public')) {
$resourceDefinitions[] = new PublicResourceDefinition(
'Resources/Public',
);
}
return $resourceDefinitions;
};
Copied!
This means that when using the system resources API
(Feature: #107537 - System resource API for system file access and public URI generation), resource identifiers are only allowed
to reference files or folders in Resources/Private and, if it exists,
Resources/Public. It also means that, by default,
Resources/Public in extensions is published in the same way as
before this change.
This publishes the extension file Build/styles.css to a folder with a
unique hash, which then contains the file styles.css.
Additionally, Build/components.css is published to
_assets/my_extension/custom/folder/my-components.css.
Example: Use a fixed prefix in _assets
By default, TYPO3 generates the public prefix automatically.
A fixed prefix can be configured explicitly:
Resource definitions configured in this file amend the default
configuration. They are added to the default configuration.
Note
It is not recommended to place additional files in the
_assets folder anymore. Instead, configure a folder outside
the document root and let TYPO3 publish it automatically into
_assets.
This is important so that third-party publishers can pick up the
publishing process and publish files to the intended location,
for example a CDN, instead of leaving them in the _assets folder.
If a resource definition configures a source folder that is already
within the system's public folder, publishing is skipped.
Closing notes
For now, this change mainly affects public files and folders.
Important
Only basic PHP operations are allowed in this file.
TYPO3 is not bootstrapped in Composer mode when this file is evaluated.
This means that, apart from classes being autoloadable, no global state
is available. Think of this file as a plain PHP file that is executed
directly as an entry point. Access to files within the package folder
handed over as an object is allowed and intentional.
Important
Relative paths must not contain leading or trailing slashes, backpaths
such as ../, or other invalid characters.
Note
Changes to the resource configuration require execution of
composer dumpautoload
or
typo3 cache:flush
in TYPO3 classic
mode.
Impact
If resource configuration is not added to an extension, this feature will have
no impact on a TYPO3 installation. If such a configuration exists,
it extends the default configuration.
Feature: #109412 - Auto-discovery of form YAML configurations
TYPO3 Form Framework can now discover YAML configuration files
from every active extension — with no PHP or TypoScript registration required.
The mechanism mirrors how Site Sets work:
each extension may provide one or more form sets by placing files in a
conventional directory layout. TYPO3 scans all the active extensions and collects
every form set it finds, powered by a Symfony service configurator
(
FormYamlCollectorConfigurator
) that runs transparently when the form
service is first resolved.
The subdirectory name (MyFormSet) is arbitrary. An extension may ship
multiple sets in separate subdirectories.
The config.yaml
Contains both the set metadata and the form configuration in a single
file. The metadata keys (name, label, priority) are reserved; all
other keys are treated as form configuration (prototype definitions, form
elements, validators, finishers, rendering options, etc.).
# Unique identifier, vendor/name convention (like composer package names)name:my-vendor/my-form-set# Human-readable label for diagnostic outputlabel:'My Custom Form Set'# Load order: lower values are loaded first and act as base configuration.# Extension sets should use a value > 10 to be merged on top of the# TYPO3 core base set (typo3/form-base, priority: 10).# Default: 100priority:200# Form configuration follows directly below the metadata:persistenceManager:allowedExtensionPaths:10:'EXT:my_extension/Resources/Private/Forms/'
Copied!
Priority and merge order
Sets are sorted by ascending priority (lower = loaded first = acts as base,
higher = override). Each set's configuration is merged on top of the previous
one using array_replace_recursive(). This is identical to how the former TypoScript
mechanism worked.
Migration from TypoScript registration
Before TYPO3 v14.2, YAML files had to be registered explicitly via TypoScript:
# Backend had to be registered separately:
module.tx_form.settings.yamlConfigurations {
1732785703 = EXT:my_extension/Configuration/Form/MySetup.yaml
}
Copied!
This registration mechanism has been deprecated and will be removed in
TYPO3 v15.0. See Deprecation-109412
for details.
After the migration:
Create the directory EXT:my_extension/Configuration/Form/MySet/.
Create config.yaml with name, optionally priority, and the
form configuration in the same file.
Remove TypoScript registrations from setup.typoscript.
name:my-vendor/my-form-setlabel:'My Custom Form Set'priority:200# Form configuration (formerly your MySetup.yaml content) goes here:prototypes:standard:formElementsDefinition:...
Copied!
Disabling a form set
Because sets from active extensions are loaded automatically, a
mechanism exists to opt out of specific sets without modifying the
extension that provides them.
To disable a set, add its declared name (from config.yaml) to
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['form']['disabledSets']
in ext_localconf.php or config/system/settings.php:
EXT:my_extension/ext_localconf.php
// Disable a third-party form set that conflicts with the site configuration:
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['form']['disabledSets'][]
= 'some-vendor/conflicting-set';
Copied!
Note
Disabling the core set typo3/form-base will break form rendering
entirely. Only disable it if you provide a full replacement set with
equivalent configuration.
The matching is done against the name field in config.yaml,
not against the directory name, so renaming the set directory does not
break an existing disable list.
EXT:form base set
The TYPO3 Form Framework ships its own base set at
EXT:form/Configuration/Form/Base/ (typo3/form-base, priority 10).
All validators, form elements, finishers, and shared rendering configuration
are defined in EXT:form/Configuration/Form/Base/config.yaml.
Site set settings for template paths
The site set typo3/form provides four settings to override Fluid template
paths and translation files.
form.templates.templateRootPath
form.templates.templateRootPath
Type
string
Default
(empty)
Override the default Fluid template path for form element rendering.
form.templates.partialRootPath
form.templates.partialRootPath
Type
string
Default
(empty)
Override the default Fluid partial path for form element rendering.
form.templates.layoutRootPath
form.templates.layoutRootPath
Type
string
Default
(empty)
Override the default Fluid layout path for form element rendering.
form.translation.translationFile
form.translation.translationFile
Type
string
Default
(empty)
Add an additional XLF translation file for form element labels.
Override the template paths in the site configuration:
Extensions that have a config.yaml in
Configuration/Form/<SetName>/ will have their configuration
automatically loaded without any additional registration.
Integrators who include the typo3/form site set can additionally override
form template paths, partial paths, layout paths and translation files through
site settings — without touching YAML prototype configuration or TypoScript
yamlSettingsOverrides
.
forge#108049 modernizes the translation workflow in the backend
in the Content > Layout and Content > Record module views. Technically, this
workflow wizard is backed by localization handlers and finishers.
The PSR-14 event
ModifyLocalizationHandlerIsAvailableEvent
is now introduced and dispatched in
LocalizationHandlerRegistry
to allow the availability state of a localization
handler to be overridden based on
LocalizationInstructions
.
The event has the following properties:
public readonly string $identifier
: String identifier returned by
LocalizationHandlerInterface::getIdentifier()
from the handler
public readonly string $className
: The concrete class name in case
a handler has been XCLASSed without changing the identifier
public readonly LocalizationInstructions $instructions
: The
localization instructions passed to the handler's isAvailable() method
to define the context
public bool $isAvailable
: The availability state returned by the
handler, which can be altered by a PSR-14 event listener
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\EventListener;
useTYPO3\CMS\Backend\Localization\Event\ModifyLocalizationHandlerIsAvailableEvent;
useTYPO3\CMS\Backend\Localization\Handler\ManualLocalizationHandler;
useTYPO3\CMS\Backend\Localization\LocalizationMode;
useTYPO3\CMS\Core\Attribute\AsEventListener;
finalclassDisableManualLocalizationHandlerForCustomTableEventListener{
#[AsEventListener(identifier: 'myext/disable-manual-localization-handler-custom-table')]publicfunction__invoke(
ModifyLocalizationHandlerIsAvailableEvent $event,
): void{
if ($event->identifier !== 'manual') {
// Return early if not ManualLocalizationHandler.return;
}
if ($event->className !== ManualLocalizationHandler::class) {
// Return early if the manual identifier is provided but// a customized (XCLASSed) class is given. This is just an// example for that property.return;
}
if ($event->instructions->mode !== LocalizationMode::TRANSLATE) {
// Return early if not handling translation// (localization) mode.return;
}
if ($event->instructions->mainRecordType === 'my_custom_table') {
// Disallow translation/localization for 'my_custom_table'// in general with the default core handler.
$event->isAvailable = false;
}
}
}
Copied!
Impact
Custom extensions (public and project-specific) are now able to intercept
and determine which handlers are available for localization steps in the translation
wizard, based on localization context.
Deprecation: #69190 - Deprecate random password generator for frontend and backend users
The passwordRules option of the passwordGenerator field control has been
deprecated. Password generation is now configured through password policies
registered in
$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']
.
Each password policy can define a generator section with a class implementing
\TYPO3\CMS\Core\PasswordPolicy\Generator\PasswordGeneratorInterface
.
The field control references a policy by name via the new passwordPolicy
option instead of defining rules inline.
Impact
Using the passwordRules option in TCA field control configuration will
trigger a PHP deprecation warning. Support for passwordRules will be
removed in TYPO3 v15.
Affected installations
Installations that use the passwordGenerator field control with the
passwordRules option in custom TCA configurations, for example in password
or secret token fields.
Migration
Replace the passwordRules option with a passwordPolicy reference.
For backend and frontend user password fields, the field control is now
provided by the core automatically. If your TCA override only added the
passwordGenerator field control with default rules, you can remove it
entirely. The core uses the password policy configured in
$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordPolicy']
and
$GLOBALS['TYPO3_CONF_VARS']['FE']['passwordPolicy']
respectively.
The
useNonce
argument in the
f:asset.script
and
f:asset.css
ViewHelpers has been renamed to
csp
to better
reflect its purpose (controlling Content-Security-Policy hash/nonce
collection rather than nonce usage specifically).
Similarly, the
'useNonce'
asset option key accepted by
addJavaScript()
and
addStyleSheet()
in class
\TYPO3\CMS\Core\Page\AssetCollector
has been
replaced by
'csp'
.
Impact
Passing
useNonce
as a ViewHelper argument or as an
AssetCollector
option key will trigger
a deprecation-level log entry in TYPO3 v14. This usage is scheduled for
removal in TYPO3 v15.
Affected installations
Installations with Fluid templates using
<f:asset.script useNonce="1">
or
<f:asset.css useNonce="1">
, and extensions calling
AssetCollector::addJavaScript()
or
AssetCollector::addStyleSheet()
with
['useNonce' => true]
.
Migration
Replace the
useNonce
argument with
csp
in Fluid templates:
<!-- Before --><f:asset.scriptidentifier="my-script"src="EXT:my_ext/Resources/Public/JavaScript/foo.js"useNonce="1" /><!-- After --><f:asset.scriptidentifier="my-script"src="EXT:my_ext/Resources/Public/JavaScript/foo.js"csp="1" />
Copied!
Replace the
'useNonce'
option key with
'csp'
in PHP:
// Before
$assetCollector->addJavaScript('my-script', $src, [], ['useNonce' => true]);
// After
$assetCollector->addJavaScript('my-script', $src, [], ['csp' => true]);
Copied!
The
PageRenderer
methods
addJsInlineCode()
,
addJsFooterInlineCode()
, and
addCssInlineBlock()
retain their
$useNonce
parameter names for backward compatibility. No migration is
required for callers of these methods.
Deprecation: #107068 - Rename fieldExplanationText to description
The configuration option
fieldExplanationText
has been deprecated
in favor of
description
. The new name better reflects its purpose
and is easier to understand.
This affects form element type definitions in
prototypes.*.formElementsDefinition.*.formEditor
configuration,
including editors, validators, and finishers in any extension.
Impact
Using
fieldExplanationText
will trigger a PHP deprecation warning.
The migration service will automatically convert
fieldExplanationText
to
description
when form configuration is loaded, ensuring
backward compatibility.
Support for
fieldExplanationText
will be removed in TYPO3 v15.0.
Affected installations
Any installations with extensions that provide custom form element type
definitions with the configuration option
fieldExplanationText
in their form prototype YAML files (e.g., Configuration/Form/*.yaml
or Configuration/Yaml/FormSetup.yaml).
Migration
Rename any occurrence of
fieldExplanationText
to
description
in your form element type definition YAML files (typically located in
Configuration/Yaml/FormElements/*.yaml).
Example migration:
# After
formEditor:
editors:
200:
identifier: placeholder
label: Placeholder
- fieldExplanationText: Enter the placeholder text+ description: Enter the placeholder text
Since Redis 6.0 it is possible to authenticate against Redis using both a username and
a password. Prior to this version, authentication was only possible via password. With
this patch, you can configure the TYPO3 Redis session backend as follows:
The "password" configuration option of the Redis session backend is now typed as
array|string
. Setting this configuration option with an array is deprecated
and will be removed in 15.0.
Affected installations
All installations using a Redis session backend and using the password configuration
option to pass an array with a username and password to it.
Migration
Use the configuration options username and password.
TYPO3 extensions that still ship an ext_emconf.php file
and do not declare future compatibility to omit this file
will now trigger a deprecation message during cache warm-up.
With TYPO3 v15 the ext_emconf.php file is no longer evaluated.
For TYPO3 classic (non-Composer) mode to keep working, TYPO3 then needs to
know the extension version and which require and suggest entries are not
TYPO3 extensions. The extension version and the providesPackages definition
therefore become mandatory fields in composer.json, regardless of whether an
ext_emconf.php file is still shipped.
In TYPO3 v14 these fields are not yet mandatory, because the deprecated
ext_emconf.php can still provide this information. A deprecation message is
triggered only when the version or providesPackages field is missing, so
that an extension can stay compatible with both TYPO3 v14 and v15 at the same
time.
To avoid this deprecation message, the extension must provide
the required package metadata in composer.json.
At minimum, this includes the extension version and the
providesPackages definition:
composer.json for an extension providing Composer packages
For compatibility with TYPO3 classic mode, third-party extensions
must set the exact extension version in extra.typo3/cms.version
or in the top level version field of composer.json.
This version must match the version previously
defined in ext_emconf.php and the released Git tag.
Fixture extensions used in tests can set any version number, for example 1.0.0,
but a version number must still be provided to avoid deprecation messages.
During testing, the version number is not evaluated.
TYPO3 Core extensions may omit the version number
in composer.json because their version number is derived from
Typo3Version
.
State migration
The former state field from ext_emconf.php is deprecated as a source of
extension metadata and should be set in composer.json using
dedicated metadata instead.
Supported stability values should be expressed via the version string:
The PHP dependency remains relevant for metadata and compatibility checks
in TYPO3 classic mode, but it is not used for extension dependency ordering.
If an extension provides regular Composer packages itself in TYPO3 classic mode,
these packages must be declared in
extra.typo3/cms.Package.providesPackages.
Packages that are already shipped by TYPO3 or already provided by another loaded
extension do not need to be repeated there.
Entries in providesPackages may also associate a provided package with a
relative path to a Composer vendor directory inside the extension. If that
directory contains a Composer-generated autoload.php, TYPO3 includes it
early during bootstrap.
If an extension does not provide any regular Composer packages itself,
providesPackages must still be present and set to an empty object
to avoid deprecation messages and to declare future compatibility
with TYPO3 classic mode.
If strict composer.json validation is required and the extension is published
to Packagist where setting the top level version field is not recommended,
it is recommended to set the version via extra.typo3/cms.version.
If the version field is set anyway, it is recommended to omit extra.typo3/cms.version
to avoid redundant data points.
Impact
There is no impact on Composer-based TYPO3 installations.
TYPO3 classic installations will trigger a deprecation message
for extensions that ship a ext_emconf.php and have not defined
the required metadata in composer.json.
Affected installations
TYPO3 classic installations are affected if they use extensions that:
still ship ext_emconf.php
do not define a "version" field or extra.typo3/cms.version
or do not define extra.typo3/cms.Package.providesPackages
at all, even as an empty object
Migration
Extension authors should move extension metadata from ext_emconf.php
to composer.json.
This includes:
the extension version via "version" or extra.typo3/cms.version
providesPackages via extra.typo3/cms.Package.providesPackages,
using it for packages provided by the extension itself; packages already
shipped by TYPO3 or already provided by another extension do not need
to be repeated
optional autoload paths for self-provided Composer packages via
extra.typo3/cms.Package.providesPackages, pointing to a Composer
vendor directory whose autoload.php can be included early
supported stability via version suffixes such as -dev, -alpha1,
-beta2, or -RC3
custom former state labels via build metadata such as +obsolete
update exclusion via extra.typo3/cms.exclude-from-updates
PHP constraints via the require.php entry
For the time being, ext_emconf.php may still need to be kept for
third-party tooling such as TYPO3 TER or Tailor. However, once the
required metadata is correctly defined in composer.json,
TYPO3 will no longer evaluate ext_emconf.php.
Deprecation: #108557 - TCA option allowedRecordTypes for Page Types
Calling any of the above methods will trigger a deprecation-level log
entry and result in a fatal PHP error in TYPO3 v15.0.
Affected installations
All installations using the
PageDoktypeRegistry
to configure page types using the
add()
method. Also, in some rare cases, using the
methods
addAllowedRecordTypes()
or
doesDoktypeOnlyAllowSpecifiedRecordTypes()
.
Migration
A new TCA option is introduced to configure allowed record types for pages:
The array can contain a list of table names or a single entry with an asterisk *
to allow all types. If no second argument was provided to the
add()
method,
then the specific configuration can be omitted, as it will fall back to the
default allowed records.
Also, note that Page Types are registered by TCA types. The former usage of
PageDoktypeRegistry
was only useful
to define allowed record types different to the default.
The option allowedRecordType is only evaluated in the "pages" table.
Deprecation: #108568 - BackendUserAuthentication::recordEditAccessInternals() and $errorMsg
The method
BackendUserAuthentication::recordEditAccessInternals()
and the property
BackendUserAuthentication::$errorMsg
of class
\TYPO3\CMS\Core\Authentication\BackendUserAuthentication
have been deprecated.
They represented an anti-pattern in which the method returned
a boolean value but communicated error details through a class property, making
the API difficult to use and test.
A new method
checkRecordEditAccess()
has been introduced. It returns
an
\TYPO3\CMS\Core\Authentication\AccessCheckResult
value object
containing both the access decision and any error messages.
Impact
Calling the deprecated method
recordEditAccessInternals()
or accessing
the deprecated property
$errorMsg
will trigger a deprecation-level log
entry and will stop working in TYPO3 v15.0.
The extension scanner reports usages as a strong match.
Affected installations
Instances or extensions that directly call
recordEditAccessInternals()
or access the
$errorMsg
property.
Migration
Replace calls to
recordEditAccessInternals()
with
checkRecordEditAccess()
. The new method returns a
\TYPO3\CMS\Core\Authentication\AccessCheckResult
object with two public
properties:
isAllowed
- Boolean indicating whether access is granted
errorMessage
- String containing the error message. It is empty if
access is granted
Before
useTYPO3\CMS\Core\Authentication\BackendUserAuthentication;
$backendUser = $this->getBackendUser();
if ($backendUser->recordEditAccessInternals($table, $record)) {
// Access granted
} else {
// Access denied, error message is in $backendUser->errorMsg
$errorMessage = $backendUser->errorMsg;
}
File-based form storage (YAML files stored via file mounts) in
EXT:form has been deprecated in favor of database storage.
Since TYPO3 v14.2, the EXT:form module stores form definitions as
records in the
form_definition
database table. This approach provides
simpler setup, integrates better with the TYPO3 permission system, and
eliminates the need for file mounts and file system configuration.
The following components are deprecated and will be removed in TYPO3 v15.0:
\TYPO3\CMS\Form\Storage\FileMountStorageAdapter
– the storage
adapter for FAL file mount-based form persistence
The YAML configuration option
persistenceManager.allowedFileMounts
– configuring allowed file mounts for form storage
An upgrade wizard as well as a CLI command are available to migrate existing file-based form definitions
to the database: System > Upgrade > Upgrade Wizard > Migrate file-based forms to database storage.
Note
YAML form files provided within extension directories
(
persistenceManager.allowedExtensionPaths
) still work as before
and are not affected by this deprecation until further concepts are evaluated.
Impact
File-based form storage will continue to work without any functional changes
during the deprecation period. However, it will be removed in TYPO3 v15.0.
An upgrade wizard is available to check whether file-based forms exist
and to migrate them to database storage. Run the wizard regularly to verify
your migration status.
Affected installations
All installations that:
Store form definitions as YAML files in file mounts
(for example, 1:/form_definitions/)
Use the
persistenceManager.allowedFileMounts
configuration
option in their form setup YAML with one or more mount points configured
Migration
Warning
If your installation uses file mount–based permission separation (i.e.,
different backend user groups have access to different form storage
folders to isolate which forms they can see and edit), an equivalent
access control mechanism for database-stored forms is not yet available.
In this case, it is recommended to not migrate at this time. The
file-based storage will continue to work without functional changes during
the entire deprecation period. A dedicated permission feature for
database storage is planned for a future release.
Run the upgrade wizard Migrate file-based forms to database storage
in the System > Upgrade module. This wizard:
Copies all file-based form definitions into the
form_definition
database table
Updates all
tt_content
FlexForm references
(persistenceIdentifier) to point to the new database records
Deletes the original YAML files after successful migration
If the
form_definition
table does not exist yet, run
System > Maintenance > Analyze Database first.
Important
The upgrade wizard only updates references in
tt_content
(CType form_formframework). If your installation stores form
persistence identifiers in custom database tables or FlexForm
fields outside
tt_content
(e.g., through third-party
extensions), these references are not updated automatically and
must be migrated manually.
After verifying that all forms work correctly from the database, remove
the
allowedFileMounts
configuration from your YAML setup:
Optionally, if the upgrade wizard did not delete the YAML files (e.g.,
due to file permission issues), delete them manually from the file system
after confirming that the migration was successful.
Alternatively, the CLI command
form:definition:transfer
can be used
to transfer forms between storage types:
# Transfer all file mount forms to database
bin/typo3 form:definition:transfer --source=filemount --target=database
# Move (transfer + delete source) in one step
bin/typo3 form:definition:transfer --source=filemount --target=database --move
# Preview without changes
bin/typo3 form:definition:transfer --source=filemount --target=database --dry-run
The following methods in
\TYPO3\CMS\Backend\Utility\BackendUtility
have
been deprecated:
getTCEFORM_TSconfig()
getTSCpidCached()
getTSCpid()
A new method
BackendUtility::getRealPageId()
has been introduced. It
returns the real page ID of a given record. Unlike the previous methods which
returned arrays with multiple values or used internal caching, this method
provides a cleaner API that returns either the page ID as an integer or
null
if the page cannot be determined.
Impact
Calling any of the deprecated methods triggers a deprecation-level log entry.
The methods will be removed in TYPO3 v15.0.
The extension scanner reports usages as a strong match.
Affected installations
Instances or extensions that call any of the deprecated methods.
Migration
getTCEFORM_TSconfig()
This method has been moved to
FormEngineUtility
. If you need TSconfig
for TCEFORM, it is recommended that you rely on FormEngine data providers
instead.
getTSCpidCached() and getTSCpid()
These methods returned an array with two values: the TSconfig PID and the
real PID. The new
getRealPageId()
method returns only the real page ID.
Before:
// getTSCpidCached returned [$tscPid, $realPid]
[$tscPid, $realPid] = BackendUtility::getTSCpidCached($table, $uid, $pid);
// getTSCpid returned the same structure
[$tscPid, $realPid] = BackendUtility::getTSCpid($table, $uid, $pid);
Copied!
After:
// getRealPageId() returns int|null
$pageId = BackendUtility::getRealPageId($table, $uid, $pid);
// If you need to ensure an integer (null becomes 0)
$pageId = (int)BackendUtility::getRealPageId($table, $uid, $pid);
The following methods in
\TYPO3\CMS\Backend\Utility\BackendUtility
have
been deprecated in favor of new methods in
\TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository
:
BackendUtility::getRecordLocalization()
- use
LocalizationRepository::getRecordTranslation()
instead
BackendUtility::getExistingPageTranslations()
- use
LocalizationRepository::getPageTranslations()
instead
BackendUtility::translationCount()
- use
LocalizationRepository::getRecordTranslations()
instead
Calling any of the deprecated methods triggers a deprecation-level log entry.
The methods will be removed in TYPO3 v15.0 and result in a fatal PHP
error.
The extension scanner reports usages as a strong match.
Affected installations
Instances or extensions that directly call any of the deprecated methods are
affected.
Migration
Inject
LocalizationRepository
and use the new methods. The new methods return
\TYPO3\CMS\Core\Domain\RawRecord
objects instead of plain arrays.
getRecordLocalization()
useTYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
useTYPO3\CMS\Backend\Utility\BackendUtility;
// Before
$translations = BackendUtility::getRecordLocalization($table, $uid, $languageId);
if (is_array($translations) && !empty($translations)) {
$translation = $translations[0];
}
// After
$translation = $this->localizationRepository->getRecordTranslation($table, $uid, $languageId);
if ($translation !== null) {
// $translation is a RawRecord object
$translatedUid = $translation->getUid();
}
Copied!
getExistingPageTranslations()
useTYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
useTYPO3\CMS\Backend\Utility\BackendUtility;
// Before
$pageTranslations = BackendUtility::getExistingPageTranslations($pageUid);
// After// Returns an array of RawRecord objects indexed by language ID
$pageTranslations = $this->localizationRepository->getPageTranslations($pageUid);
The method
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToUserSettings()
has been deprecated in favor of the new
addUserSetting()
method.
The legacy method required two separate steps to add a field to user settings:
first, adding the field configuration to the columns array and second, calling
addFieldsToUserSettings()
to add it to the showitem list. The new
method combines both steps into a single call and uses TCA as the storage
location.
Impact
Calling the deprecated method will trigger a deprecation-level log entry.
The method will be removed in TYPO3 v15.0.
The extension scanner reports usages as a strong match.
Affected installations
Instances or extensions that use
ExtensionManagementUtility::addFieldsToUserSettings()
or directly
modify
$GLOBALS['TYPO3_USER_SETTINGS']
to add custom fields to the
backend user profile settings.
Migration
Replace the two-step approach with the new
addUserSetting()
method.
Note that the new method uses TCA-style configuration and should be called
from Configuration/TCA/Overrides/be_users.php instead of
ext_tables.php.
Extension developers can now use labels in JavaScript components without
requiring labels to be preloaded globally or per module. This reduces the risk
of missing labels and simplifies developer workflows.
Affected installations
The deprecated method was introduced in TYPO3 v14.1. This means that only
installations that use
addInlineLanguageDomain()
in TYPO3 v14.1 or later
are affected.
Migration
The call to
PageRenderer::addInlineLanguageDomain()
can be removed. In
the JavaScript code, add a module import that imports from the
'~labels/'
prefix.
import { html } from'lit';
// Import labels from language domain "core.bookmarks"import labels from'~labels/core.bookmarks';
// Use label
html`<p>{labels.get('groupType.global')}</p>`
Copied!
Deprecation: #109027 - Move language:update command and events to EXT:core
The language:update CLI command and related
\TYPO3\CMS\Install\Service\LanguagePackService
have been moved from
EXT:install to EXT:core, allowing installations to update language packs
without EXT:install having to be installed.
Since TYPO3 v13 it has been possible to run TYPO3 without EXT:install in
Composer-based installations. However, the language:update command still
required EXT:install, which was impractical for deployments that needed to
update language packs.
The following classes have been moved, and their old class names deprecated:
\TYPO3\CMS\Install\Command\LanguagePackCommand
is now
\TYPO3\CMS\Core\Command\UpdateLanguagePackCommand
\TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent
is now
\TYPO3\CMS\Core\Localization\Event\ModifyLanguagePackRemoteBaseUrlEvent
\TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
is now
\TYPO3\CMS\Core\Localization\Event\ModifyLanguagePacksEvent
The old class names are registered as aliases via
ClassAliasMap
and
continue to work in TYPO3 v14. Event listeners registered for the deprecated
event class names are still called when the new event is dispatched, with a
deprecation notice triggered at runtime.
Impact
Using the old class names will trigger a deprecation notice. The extension
scanner will report usage of the deprecated class names.
The old class names will be removed in TYPO3 v15.
Affected installations
Extensions that use one or more of the deprecated class names listed above.
Migration
Replace the old class names with the new ones in
use
statements:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
-use TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent;+use TYPO3\CMS\Core\Localization\Event\ModifyLanguagePackRemoteBaseUrlEvent;
final class MyOtherEventListener
{
#[AsEventListener(
identifier: 'my-extension/modify-language-pack-remote-base-url',
)]
public function __invoke(
ModifyLanguagePackRemoteBaseUrlEvent $event,
): void {
// ...
}
}
Copied!
Note
Extensions supporting both TYPO3 v13 and v14 do not need to change
anything. The old class names continue to work in both versions.
Simply update the
use
statements when dropping TYPO3 v13 support.
Deprecation: #109029 - FormEngine doSave hidden field
The
<input type="hidden" name="doSave">
field in FormEngine was a
legacy mechanism where JavaScript set the field value to 1 to
signal to PHP that the submitted form data should be processed as a save
operation.
This indirection is no longer needed. TYPO3 now uses native submit button
values such as _savedok directly, which are sufficient to determine
whether a save operation should be performed. The field is no longer
evaluated internally.
For backward compatibility, the
doSave
field is still appended to the
form on programmatic saves in TYPO3 v14, but this behavior is deprecated and
will be removed in TYPO3 v15.
Impact
Third-party code reading
$request->getParsedBody()['doSave']
to detect
whether a save operation was triggered will stop working in TYPO3 v15.
Affected installations
Installations with custom backend modules or extensions that inspect the
doSave
POST field to determine whether incoming form data should be
persisted.
Migration
Replace any checking of the
doSave
POST field with a check of
all native submit action fields that FormEngine sends as part of normal
form submission.
Before:
$parsedBody = $request->getParsedBody();
$doSave = (bool)($parsedBody['doSave'] ?? false);
if ($doSave) {
// process data
}
Copied!
After:
$parsedBody = $request->getParsedBody();
$isSaveAction = !empty($parsedBody['_savedok'])
|| !empty($parsedBody['_saveandclosedok'])
|| !empty($parsedBody['_savedokview'])
|| !empty($parsedBody['_savedoknew']);
if ($isSaveAction) {
// process data
}
The additionalHiddenFields result array key in FormEngine was a legacy
mechanism that stored hidden
<input>
HTML strings separately from the
main html key. This indirection is no longer needed. Elements can simply
add their hidden fields to the html key.
The following have been deprecated:
The additionalHiddenFields key in FormEngine result arrays
FormResult::$hiddenFieldsHtml
FormResultCollection::getHiddenFieldsHtml()
Impact
Third-party FormEngine elements that add entries to
$resultArray['additionalHiddenFields']
will trigger a PHP
E_USER_DEPRECATED
level error when their result is merged via
AbstractNode::mergeChildReturnIntoExistingResult()
.
Affected installations
Installations with custom FormEngine elements or containers that populate the
additionalHiddenFields result array key.
Migration
Move hidden field HTML from additionalHiddenFields into the html key.
The
DatePicker
form element type and its associated
DatePickerViewHelper
and
TimePickerViewHelper
have been
deprecated as part of removing jQuery dependency from
typo3/cms-form
. The
Date
form element type serves as a
replacement and uses native HTML5
<input type="date">
without needing
a JavaScript library.
The EXT:form/Resources/Public/JavaScript/frontend/date-picker.js
jQuery initialization script
Impact
Using the
DatePicker
form element type in a form definition will
trigger a PHP
E_USER_DEPRECATED
level error at runtime. The element,
its ViewHelpers, and the JavaScript file will be removed in TYPO3 v15.
Affected installations
All installations that use the
DatePicker
form element type in form
definitions created with the TYPO3 Form Framework.
Migration
Replace
DatePicker
with the
Date
form element type in your
form definitions.
Before:
type:DatePickeridentifier:date-1label:'Pick a date'properties:dateFormat:Y-m-denableDatePicker:true
Copied!
After:
type:Dateidentifier:date-1label:'Pick a date'
Copied!
The
Date
element uses a native HTML5 date input, which does not
require jQuery or additional JavaScript. The
dateFormat
and
enableDatePicker
properties are no longer
needed because the browser handles date formatting and the picker natively.
Alternatively, if the native HTML5 date input does not meet your
requirements, you can create a custom form element with a date picker
JavaScript library of your choice.
Bootstrap's tab JavaScript has been replaced with a custom implementation
tailored to TYPO3. The Bootstrap tab events
show.bs.tab
and
shown.bs.tab
are now deprecated and will be removed in TYPO3 v15.
The following new custom events are available as replacements:
typo3:tab:show
— dispatched before a tab switch, cancelable via
event.preventDefault()
typo3:tab:shown
— dispatched after a tab switch
Both events bubble from the tab button and carry a
detail.relatedTarget
property that points to the previously active tab
button or
null
.
Impact
Listening for
show.bs.tab
or
shown.bs.tab
events will continue to
work in TYPO3 v14 but will stop working in TYPO3 v15, when the backward-
compatibility events will be removed.
Affected installations
All extensions that listen to
show.bs.tab
or
shown.bs.tab
events
on tab buttons are affected.
Migration
Replace Bootstrap tab event listeners with the new TYPO3 tab events.
Before:
document.addEventListener('show.bs.tab', (e) => {
console.log(
'Tab is about to show',
e.target,
e.detail.relatedTarget
);
});
document.addEventListener('shown.bs.tab', (e) => {
console.log(
'Tab was shown',
e.target,
e.detail.relatedTarget
);
});
Copied!
After:
document.addEventListener('typo3:tab:show', (e) => {
console.log('Tab is about to show', e.target, e.detail.relatedTarget);
});
document.addEventListener('typo3:tab:shown', (e) => {
console.log('Tab was shown', e.target, e.detail.relatedTarget);
});
The
\TYPO3\CMS\Backend\Form\Container\OuterWrapContainer
FormEngine
container has been deprecated in favor
of the new
\TYPO3\CMS\Backend\Form\Container\FormWrapContainer
.
The old container rendered record
headers, type icons, and record identity information inside FormEngine, which
forced controllers to hide redundant elements via CSS hacks.
The new
FormWrapContainer
only handles form wrapping (description,
read-only notice, field information, field wizards, and child HTML).
Rendering record headers and identity information is now the responsibility of
the controllers themselves.
Impact
Using the outerWrapContainer render type will trigger a PHP
E_USER_DEPRECATED
level error. The container will still work as before
during the deprecation period.
Affected installations
Installations with custom controllers or FormEngine integrations that set
$formData['renderType'] = 'outerWrapContainer'
.
Migration
Replace the render type outerWrapContainer with
formWrapContainer.
Note that
FormWrapContainer
no longer renders the record heading
(
<h1>
) or the record identity footer (icon, table title, uid). If
your controller relied on these being rendered by
OuterWrapContainer
, you need to
render them in your controller code.
Deprecation: #109196 - Deprecate doktypesToShowInNewPageDragArea user TSconfig
The user TSconfig option
options.pageTree.doktypesToShowInNewPageDragArea
has been deprecated and will be removed in TYPO3 v15.0.
The page tree toolbar submenu now automatically determines available doktypes
based on the user's group permissions. Manual TSconfig configuration is no
longer needed.
Impact
Using the deprecated user TSconfig option triggers a deprecation-level log
entry and will stop working in TYPO3 v15.0.
Affected installations
TYPO3 installations that set
options.pageTree.doktypesToShowInNewPageDragArea
in their user
TSconfig.
Migration
Remove the
options.pageTree.doktypesToShowInNewPageDragArea
option
from your user TSconfig. The page tree toolbar will then display all
doktypes that the current backend user is allowed to create based on their
group permissions.
The class
\TYPO3\CMS\Backend\Form\FormResultCompiler
has been
deprecated. The internal implementation of FormEngine has been adjusted to
better separate concerns, especially regarding rendering and asset handling.
This change also removed all internal usages of
\TYPO3\CMS\Backend\Form\FormResultCompiler
,
as it handled more tasks than its name suggested.
Impact
Extensions and installations that render FormEngine forms manually rather
than through standard controllers, such as
EditDocumentController
, and that use
FormResultCompiler
, will be affected when the
class is removed in TYPO3 v15.
Affected installations
Installations and extensions using
FormResultCompiler
to build FormEngine forms.
Migration
Replace
FormResultCompiler
with
FormResultFactory
and
FormResultHandler
.
Before:
useTYPO3\CMS\Backend\Form\NodeFactory;
useTYPO3\CMS\Backend\Form\FormResultCompiler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$formResultCompiler = GeneralUtility::makeInstance(
FormResultCompiler::class
);
$formResult = $nodeFactory->create($formData)->render();
$formResultCompiler->mergeResult($formResult);
// Form HTML markup is accessible in the data array
$body = $formResult['html'];
Copied!
After:
useTYPO3\CMS\Backend\Form\NodeFactory;
useTYPO3\CMS\Backend\Form\FormResultFactory;
useTYPO3\CMS\Backend\Form\FormResultHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$formResultFactory = GeneralUtility::makeInstance(
FormResultFactory::class
);
$formResultHandler = GeneralUtility::makeInstance(
FormResultHandler::class
);
$formResult = $nodeFactory->create($formData)->render();
// Convert the raw result array into a FormResult object
$formResult = $formResultFactory->create($formResult);
// Use FormResultHandler to pass collected assets (JS, CSS, labels) to PageRenderer
$formResultHandler->addAssets($formResult);
// Form HTML markup is accessible in the FormResult DTO
$body = $formResult->html;
The
\TYPO3\CMS\Backend\Form\FieldInformation\TcaDescription
field
information render type has been deprecated. Field descriptions configured
via TCA
['columns']['fieldName']['description']
are now rendered
automatically next to the field label by
\TYPO3\CMS\Backend\Form\Element\AbstractFormElement::renderDescription()
and
\TYPO3\CMS\Backend\Form\Container\AbstractContainer::renderDescription()
.
Previously, every FormEngine element and container registered
tcaDescription as a default field information node, which rendered the
description inside the element body. The description is now rendered
after the label or legend element, providing more consistent positioning
across all field types.
Additionally, the
$defaultFieldInformation
property has been removed
from all Core FormEngine elements and containers. Custom elements that extend
Core elements and rely on tcaDescription being present in
$defaultFieldInformation
are also affected.
Impact
Using the tcaDescription render type in a custom fieldInformation
configuration will trigger a PHP
E_USER_DEPRECATED
level error. The
render type still exists but will return empty output during the deprecation
period, since descriptions are now rendered at the label level.
Custom FormEngine nodes that extend core elements and override
$defaultFieldInformation
to include tcaDescription will still work,
but the tcaDescription entry will trigger a deprecation warning.
Affected installations
Installations with extensions that explicitly configure tcaDescription
as a field information node in TCA:
Extensions that only use the standard TCA description property are not
affected — descriptions will continue to be rendered.
Migration
Remove any explicit tcaDescription field information configuration from
TCA and from custom FormEngine node classes. Field descriptions are now
rendered automatically next to the label and no longer require a field
information node.
Request dependencies within
PageRenderer
are no longer implicit via
$GLOBALS['TYPO3_REQUEST']
and must now be passed explicitly. Not
passing a request to the methods listed above will trigger a deprecation-level
log entry in TYPO3 v14 and will result in a fatal PHP error in TYPO3 v15.
Affected installations
PageRenderer
is a low-level Core class. Many
extensions use higher-level APIs and are therefore not directly affected by
this change.
Migration
Adapt method calls to pass the
ServerRequestInterface
object explicitly.
The methods
setLogTable()
and
getLogTable()
in
\TYPO3\CMS\Core\Log\Writer\DatabaseWriter
have been deprecated.
DatabaseWriter
is a dedicated writer
for the
sys_log
table. Its
writeLog()
method maps
LogRecord
fields to the
sys_log
schema (
request_id
,
time_micro
,
component
,
level
,
message
,
data
,
tstamp
). Allowing an
arbitrary table to be set via
setLogTable()
created a false sense of
flexibility - any custom table needs to replicate the full
sys_log
schema to work correctly.
The long-term goal is to make
DatabaseWriter
final
and to
remove the
$logTable
property entirely.
Impact
Calling
setLogTable()
or
getLogTable()
triggers a PHP
E_USER_DEPRECATED
error. This also includes passing
logTable
as a configuration option when
DatabaseWriter
is registered via
$GLOBALS['TYPO3_CONF_VARS']['LOG']
, since the
AbstractWriter
constructor resolves options to
set*()
calls.
Support will be removed in TYPO3 v15.0.
Affected installations
Installations that configure
DatabaseWriter
with a custom
logTable
option, or that call
setLogTable()
or
getLogTable()
on a
DatabaseWriter
instance.
The extension scanner detects direct calls to
->setLogTable()
and
->getLogTable()
. The more common case, passing
logTable
as a
configuration option via
$GLOBALS['TYPO3_CONF_VARS']['LOG']
, cannot
be detected automatically and requires a manual search for
DatabaseWriter
usage with a
logTable
key.
Migration
Replace
DatabaseWriter
with a
dedicated writer that extends
\TYPO3\CMS\Core\Log\Writer\AbstractWriter
and implements
writeLog()
with explicit field mapping for the custom table.
The Form Editor stage component provided a set of JavaScript helper functions
for template-based rendering of form elements in the stage area. These
functions were designed to be called from subscribers to the
view/stage/abstract/render/template/perform
PubSub event, which is the
extension point for custom form element rendering in the stage.
With the introduction of the
<typo3-form-form-element-stage-item>
and
<typo3-form-page-stage-item>
web components (see
Feature: #107058 - Simplify registration of a custom form element), the built-in template-based helper
functions have been superseded. The
view/stage/abstract/render/template/perform
event
remains available, and extension authors may continue to subscribe to it
to implement fully custom stage rendering logic.
The following exported functions from
@typo3/form/backend/form-editor/stage-component
are deprecated:
eachTemplateProperty()
renderSimpleTemplate()
renderSimpleTemplateWithValidators()
renderCheckboxTemplate()
renderSelectTemplates()
renderFileUploadTemplates()
createAbstractViewFormElementToolbar()
— only used by the legacy
template-based rendering path. Web component-based elements handle
their toolbar via the
toolbarConfig
property of
<typo3-form-form-element-stage-item>
In addition, all Fluid partial templates in
EXT:form/Resources/Private/Backend/Partials/FormEditor/Stage/ are
deprecated, as they were designed for use with the template-based rendering
approach described above:
SimpleTemplate.fluid.html
SelectTemplate.fluid.html
FileUploadTemplate.fluid.html
ContentElement.fluid.html
Fieldset.fluid.html
StaticText.fluid.html
Page.fluid.html
SummaryPage.fluid.html
_ElementToolbar.fluid.html
_UnknownElement.fluid.html
Impact
Extensions that call any of the deprecated helper functions will receive IDE
deprecation hints and TypeScript compiler warnings. The deprecated Fluid
templates will emit an HTML comment in the rendered stage area indicating
their deprecation. All deprecated functions and templates will be removed in
TYPO3 v15.
Affected installations
All extensions that:
call any of the deprecated JavaScript helper functions (including
createAbstractViewFormElementToolbar()
), typically from a subscriber
of the
view/stage/abstract/render/template/perform
event, or
reference any of the deprecated Fluid partial templates via
formEditorPartials
in their prototype configuration.
Migration
Two migration paths are available:
Option 1: Use the built-in web component (recommended)
Remove the custom JavaScript subscriber and omit the
formEditorPartials
stage partial configuration from your form
element's YAML definition. The Form Editor will then render the element
automatically using the built-in
<typo3-form-form-element-stage-item>
web component.
Option 2: Implement custom rendering logic in the event subscriber
If you need to keep using the
view/stage/abstract/render/template/perform
event, replace calls to
the deprecated helper functions with your own DOM manipulation logic.
Invoking any of the methods listed above will generate a
deprecation-level log entry in TYPO3 v14. These methods are scheduled
for removal in TYPO3 v15.
From an architectural perspective, the
PageRenderer
singleton represents
a central yet problematic construct, particularly in TYPO3 frontend
rendering. With the deprecation of
these methods, the
PageRenderer
class loses its
ability to serve as a data source - data can still be added but no longer retrieved.
This change paves the way for refactoring the construct in TYPO3 v15,
including the introduction of a compatibility layer to maintain
backward compatibility.
Affected installations
Instances with extensions invoking one of the methods listed above are
affected. The extension scanner is configured to find consumers, apart from the
generic method names
getTitle()
,
getLanguage()
, and
getPageRenderer()
.
Migration
In practice, there is often little reason to rely on the methods
mentioned above. Most data passed to PageRenderer is handled through
mechanisms that can be intercepted and configured, for example title
and meta tag handling. As a result, the deprecated get() methods do
not have a direct replacement.
A commonly used case is
PageRenderer->getDocType()
, which
determines whether self-closing tags should include a trailing slash
(/). This is relevant only in the frontend, as the backend
always uses HTML5. The DocType itself is derived from TypoScript
configuration, which is available as a request attribute.
TYPO3 installations using resource identifiers that reference extension
folders outside Configuration,
Resources/Private, or Resources/Public
will receive a deprecation message when such a resource is resolved.
TYPO3 installations using resource identifiers that reference extension
folders outside Configuration,
Resources/Private, or Resources/Public.
Migration
Either configure the referenced paths explicitly in
Configuration/Resources.php, as described in
Feature: #109409 - Allow configuration of resources, or move the resources to a path that
is already configured.
Deprecation: #109409 - Allowed paths configuration is deprecated
TYPO3 installations that use
$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']
will receive a deprecation message whenever resources for the
typo3/app package are resolved.
Affected installations
TYPO3 installations that use
$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']
.
Migration
Configure resources in config/system/resources.php instead of
using
$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']
.
The TypoScript-based registration of form YAML configuration files via
plugin.tx_form.settings.yamlConfigurations
and
module.tx_form.settings.yamlConfigurations
has been
deprecated in favor of the new auto-discovery mechanism introduced in
TYPO3 v14.2 (see Feature-109412).
Before TYPO3 v14.2 this was the only way to register EXT:form YAML files. It
required separate registration of the frontend and the backend
in TypoScript:
plugin.tx_form.settings.yamlConfigurations {
1732785702 = EXT:my_extension/Configuration/Form/MySetup.yaml
}
# Backend had to be registered separately:
module.tx_form.settings.yamlConfigurations {
1732785703 = EXT:my_extension/Configuration/Form/MySetup.yaml
}
Copied!
The TypoScript-based paths will still be loaded during the deprecation
period but will be removed in TYPO3 v15.0.
Impact
Extensions that register form YAML files via TypoScript will trigger a
PHP
E_USER_DEPRECATED
error. The registered YAML files are still
loaded and will remain functional during the deprecation period.
Affected installations
All installations where an extension registers form YAML files via:
plugin.tx_form.settings.yamlConfigurations
module.tx_form.settings.yamlConfigurations
Migration
Replace TypoScript registration with the auto-discovery directory
convention introduced in TYPO3 v14.2
(see Feature-109412).
name:my-vendor/my-form-setlabel:'My Custom Form Set'priority:200# Content of your former MySetup.yamlpersistenceManager:allowedExtensionPaths:10:'EXT:my_extension/Resources/Private/Forms/'
Copied!
Remove TypoScript registrations from
setup.typoscript. PHP or TypoScript registration is
no longer necessary.
The YAML files are picked up automatically for both frontend and
backend without any additional registration.
Important: #70867 - XLIFF whitespace handling now respects xml:space
This affects how whitespace (spaces, tabs, newlines) in translation strings
is handled.
Without
xml:space="preserve"
(default behavior):
Multiple consecutive whitespace characters (spaces, tabs, newlines) are
collapsed into a single space, and leading/trailing whitespace is trimmed.
Example XLIFF source:
<trans-unitid="my.label"><source>This is a
multi-line
string.</source></trans-unit>
Copied!
Before: The string contained literal newlines and indentation.
After: The string becomes
"This is a multi-line string."
With
xml:space="preserve"
:
Whitespace is kept exactly as written in the XLIFF file.
<trans-unitid="my.label"xml:space="preserve"><source>This is a
multi-line
string.</source></trans-unit>
Copied!
The string remains
"This is a\n multi-line\n string."
Impact
Translation strings that previously contained unintended whitespace (from
formatting in the XLIFF file) are now correctly displayed without extra spaces
or line breaks.
If you need preserved whitespace in a translation string,
add the
xml:space="preserve"
attribute to the
<trans-unit>
element (XLIFF 1.2) or
<segment>
element (XLIFF 2.0).
This change affects both XLIFF 1.2 and XLIFF 2.0/2.1 formats.
Important: #93765 - Extbase identity map now language-aware
The Extbase persistence session's identity map now includes language context
when domain objects are cached. Previously, the identity map used identifiers
based only on a record's UID and localized UID, which could cause incorrect
translations to be returned when the same object was accessed by different
LanguageAspect
configuration inside the same request.
The identity map identifier now includes the language context,
specifically the contentId, overlayType, and fallbackChain
properties of
LanguageAspect
.
Impact
This change ensures that objects loaded with different language configurations
are cached separately in the identity map. For example, if an object is first
loaded with OVERLAYS_ON and then queried again with OVERLAYS_MIXED,
the system will correctly return different cached objects for each context.
The change is transparent for most use cases. However, objects retrieved with
different language settings are now distinct instances. Code relying on
object identity (for example, using === comparison) between objects loaded
with different language settings will need adjustment.
Important: #102906 - Prevent Extbase errorAction from writing session data
Previously, validation errors handled implicitly by the Extbase
ActionController::errorAction()
persisted the resulting
FlashMessage
items to the user session.
If no session existed, a new session was generated and a session
cookie was sent to the client. This behavior could lead to automated crawlers
generating a large number of unnecessary sessions.
When
errorAction()
is invoked (for example, due to validation
errors), flash messages are no longer persisted to the session but are
instead transferred with the corresponding
ForwardResponse
.
The implementation introduces two new public methods in
\TYPO3\CMS\Extbase\Http\ForwardResponse
:
withFlashMessages(FlashMessage ...$flashMessages)
- Adds flash
messages to the forward response
getFlashMessages()
- Retrieves flash messages from the forward
response
Flash messages are transferred through
ExtbaseRequestParameters
when
forwarding requests and are restored from ExtbaseRequestParameters in
ActionController::initializeStateFromExtbaseRequestParameters()
.
Hint
Custom code that overrides the internal methods
ActionController::processRequest()
or
ActionController::forwardToReferringRequest()
may need to be
adjusted to benefit from this change. Ensure that your custom
implementations properly handle flash messages via
\TYPO3\CMS\Extbase\Http\ForwardResponse
when forwarding
requests from the error action.
When a TCA select field is configured as renderType => 'selectSingle'
and an item is added with 'value' => null, the database column that is generated is
now nullable regardless of whether the other item values are integers or strings.
Previously, the following configuration with integer item values incorrectly
generated a
VARCHAR(255)
column:
The workspace selector has been moved from the top toolbar to the backend
sidebar. The selector now displays the full name of the currently active
workspace and provides a dropdown to switch between available workspaces.
A colored top bar indicator is shown whenever a workspace is active, giving
editors a clear visual cue about which workspace they are working in.
Color per workspace
Administrators can now assign a color to each workspace via the
Color field in the workspace record. The selected color is used
for the top bar indicator and the workspace selector in the sidebar, making
it easier to visually distinguish workspaces at a glance.
The following colors are available: red, orange, yellow, lime, green, teal,
blue, indigo, purple, and magenta. The default color for new workspaces is
orange.
Description as tooltip
The Description field of a workspace record is now displayed as
a tooltip on both the workspace selector dropdown items and the top bar
indicator. This allows administrators to provide additional context about
the purpose of a workspace, which editors can see by hovering over the
workspace name.
Workspace Live indicator option
The user setting Enable workspace Live indicator is available in
User Settings > Personalization. When enabled (the default), the
top bar indicator is shown while working in the Live workspace. Users who find
the indicator distracting can disable it. The setting takes effect immediately
without requiring a page reload. The indicator remains visible when any
non-Live workspace is active, regardless of this setting.
Impact
Editors will see the workspace selector in the sidebar instead of the top
toolbar. The workspace indicator bar at the top of the backend now reflects
the color that is configured for the active workspace.
Administrators are encouraged to assign meaningful colors and descriptions to
their workspaces to improve the editing experience for their teams.
Important: #108557 - Drop PageDoktypeRegistry onlyAllowedTables option
It is possible to limit which tables are allowed for page types (doktype). However, up
until now the default behavior when switching types was to ignore
violations of these rules. The behavior could be changed for a particular doktype
like this:
This made page type 116 strict when switching the page type.
This option is now obsolete, as this functionality is always enabled.
Switching page types is no longer possible if it violates the configured
allowed tables, which makes the system more consistent.
Some remarks: This option was rarely used and often misunderstood. The option
to configure allowed tables was called allowedTables. It was not clear what
onlyAllowedTables meant without looking in the documentation.
From a practical point of view, it does not make sense to configure
restrictions for page types when they are ignored by default for the action of
switching types. Allowing the rules to be violated makes them ineffective in
the first place. So either remove those restrictions altogether or make them
always apply, which is what happens now.
Important: #108783 - Backend user language default changed to "en"
The backend user language field (be_users.lang) historically used default
as the value for English. This has been changed to use the standard ISO 639-1
language code en instead.
The language key default is still accepted for backward compatibility with
custom code, but can no longer be selected in the backend user interface.
An upgrade wizard, Migrate backend user language from 'default' to 'en',
is available to migrate existing backend user records.
Impact
New backend users have en as their default language instead of
default.
Existing backend users with lang=default should run the upgrade wizard
to migrate to lang=en.
In general, code that uses default as a language key (e.g. custom
instances of
LanguageService
) will continue to work, as
default is still mapped to en internally.
Important: #108796 - Internal shortcut classes renamed to bookmark
As part of the centralized bookmark management feature, several internal classes
related to backend shortcuts have been renamed to use "bookmark" terminology.
These classes were marked as
@internal
and are not part of the public
TYPO3 API. However, some extensions might have used them although they were internal.
The following classes have been renamed or replaced:
Renamed to
\TYPO3\CMS\Backend\Backend\ToolbarItems\BookmarkToolbarItem
The JavaScript module
@typo3/backend/toolbar/shortcut-menu
has been removed
and replaced by the new bookmark management modules in
@typo3/backend/bookmark/
.
Important: #109147 - Move link validator module to Link Management group
The link validator module (Check Links) has been moved from the
Status / Info module group (content_status) to the
Link Management module group (link_management), alongside
Redirects and QR Codes.
Since the Link Management parent module does not provide a page
tree navigation component, the link validator module now brings its own
navigationComponent to retain the page tree.
Additionally, the module identifier has been changed from
web_linkvalidator to linkvalidator_checklinks.
Impact
The link validator module now appears under Link Management in the
backend module menu instead of Status / Info.
An upgrade wizard ensures that backend users and groups with
web_linkvalidator permissions are migrated to linkvalidator_checklinks
and automatically receive access to the link_management parent module.
After TYPO3 v14.0, only new functionality with a solid migration path
can be added on top, with aiming for as little as possible breaking changes
after the initial v14.0 release on the way to LTS.
The TYPO3 Install Tool password can now utilize validators as defined
via the
$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']['installTool']['validators']
array. By default, this re-uses the default validator
\TYPO3\CMS\Core\PasswordPolicy\Validator\CorePasswordValidator
with the configuration:
This will require 8 characters minimum (as it was before) and now also require
at least one upper-case, one lower-case, one digit and one special character.
The validator is utilized in both scenarios when setting the Install Tool password
via CLI (bin/typo3 install:password:set) as well as the Install Tool GUI via
Admin Tools > Settings > Change Install Tool Password in the TYPO3 backend.
If a password is auto-generated via the mentioned CLI command, by default it uses
8 characters. The password-length for adapted validator configurations can then be
specified with the new --password-length=XXX argument.
Impact
Maintainers now need to set secure Install Tool passwords and can configure
validation rules.
Existing Install Tool passwords are not affected, making this a non-breaking feature.
However, these should be revisited by maintainers and maybe set to a more secure
password.
To disable password policies (not recommended!), the configuration option
$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']['installTool']['validators']
can be set to an empty array (or null).
Hint
Please note, you can only override this configuration directive through the
additional.php configuration file, otherwise an empty array or null value
will trigger merging the DefaultConfiguration over the defined settings,
and re-adding default validators to your setup.
A new Link Management > QR Codes backend module has been introduced,
grouped alongside the existing Link Management > Redirects module.
The QR Codes module provides editors with an efficient way to generate reusable
QR codes for various purposes, such as printing them on promotional materials,
booth displays, or marketing collateral.
Each generated QR code contains a permanent, unique URL that never changes,
ensuring printed materials remain valid indefinitely. While the QR code URL
itself stays constant, the destination it redirects to can be updated at any
time, providing flexibility to adapt campaigns or redirect visitors to current
content without requiring reprints.
The module includes a convenient button to generate QR codes on demand, offering
multiple download options including different formats (PNG, SVG) and customizable
sizes to suit various use cases and printing requirements.
Impact
The new QR Code module enables users to create scannable QR codes that redirect
to any specified URL. This feature is particularly valuable for marketing campaigns,
events, and printed materials where maintaining flexibility in the destination URL
is essential while preserving the QR code itself.
Site sets can now define route enhancers in a dedicated route-enhancers.yaml
file. This allows extensions to provide route enhancers as part of their site set
configuration, which are automatically merged into the site configuration when
the set is used as a dependency.
The route enhancers from site sets are applied as presets. This means that
site-level route enhancer configuration takes precedence and can override
set-defined enhancers.
Usage
Create a route-enhancers.yaml file in your site set directory alongside
the config.yaml:
imports:-{resource:'route-enhancers/*.yaml',glob:true}routeEnhancers:# Additional enhancers can be defined here
Copied!
Merging behavior
Route enhancers from site sets are merged in dependency order. When a site
uses multiple sets, enhancers from earlier dependencies are loaded first,
and later sets can override them.
Site-level route enhancer configuration always takes precedence over
set-defined enhancers. This allows sites to customize or override
preset configurations from sets.
Scalar values from the site configuration override set-defined values,
while new keys are appended.
Impact
Extensions can now ship route enhancers as part of their site sets, providing
a streamlined way to configure routing for extension functionality. This is
particularly useful for extensions that require specific URL patterns, such
as sitemap extensions or API endpoints.
Invalid route enhancer configurations are handled gracefully: sets with
invalid route-enhancers.yaml files are skipped and logged, similar
to other set validation errors.
Feature: #107837 - Sitemap route enhancers provided via site set
The SEO extension now ships its sitemap route enhancers as part of
the typo3/seo-sitemap site set. When this set is used as a dependency,
the route enhancers for XML sitemaps are automatically configured.
This enables clean URLs for sitemaps out of the box:
/sitemap.xml - Main sitemap index
/sitemap-type/pages - Pages sitemap
Previously, these route enhancers had to be manually configured in each
site's config.yaml.
Impact
Sites using the typo3/seo-sitemap set no longer need to manually
configure sitemap route enhancers. The clean URLs are available
automatically.
Sites can still override or extend the route enhancers in their
site configuration if needed.
Feature: #107961 - Search translated pages in page tree
The page tree filter has been extended with the ability to search for pages
through their translated content. This enhancement makes it significantly easier
to find pages in multilingual TYPO3 installations, particularly when editors
work primarily with translated content.
The page tree filter now supports two translation search methods:
Search by translated page title: When a search phrase matches a translated page
title or nav_title, the corresponding default language page will be found and
displayed in the page tree.
Search by translation UID: When searching for a numeric page UID that belongs
to a translated page, the parent default language page will be found and displayed.
Both search methods work seamlessly alongside the existing search capabilities
(searching by page title, nav_title, or default language UID).
Configuration
Translation search is enabled by default and can be controlled in two ways:
User TSconfig
Administrators can control the availability of translation search via User TSconfig:
# Disable searching in translated pages for specific users/groups
options.pageTree.searchInTranslatedPages = 0
Copied!
User Preference
Individual backend users can toggle this setting using the page tree toolbar menu.
The preference is stored in the backend user's configuration, allowing each user
to customize their search behavior.
Visual Feedback
When a page is found through a translation match, a colored label is automatically
added to provide clear visual feedback:
Single translation match
Displays "Found in translation: [Language Name]"
Example: When searching for "Produkte", a page found via its German translation
shows "Found in translation: German"
Multiple translation matches
Displays "Found in multiple translations"
Example: When searching for "Home", a page with matching French and German
translations shows "Found in multiple translations"
Direct matches
Pages matching the search phrase directly (L=0) show "Search result"
Example: When searching for "Products", the English page titled "Products"
shows "Search result"
Combined matches
When a page matches both directly and through a translation, both labels
are displayed.
Example: Searching for "Home" finds a page titled "Home" with a German
translation "Startseite Home" - the page shows both labels.
Note
Search term highlighting works only for direct matches. For performance
reasons, it is activated only if the search yields fewer than 100 results,
and it requires the user to enter at least two characters or a number.
Flexibility for developers
Developers can still use the PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent
to add custom labels or modify the prepared tree items before they are rendered.
Impact
Editors working in multilingual TYPO3 installations can now efficiently search
for pages using translated titles or translation UIDs. The visual labels provide
immediate feedback about how search results were matched, improving the user
experience when navigating complex page trees.
The feature respects user permissions (language restrictions from user groups)
and workspace context, ensuring that only accessible translations are searched.
Feature: #108344 - Allow number of decimals in
stdWrap.bytes
function
The TypoScript function
stdWrap.bytes
now accepts an additional
configuration parameter
decimals
. It allows to explicitly define
the number of decimals in the resulting number representation. By default, the
number of decimals is derived from the formatted size.
In addition, the consumed PHP function
TYPO3\CMS\Core\Utility\GeneralUtility::formatSize
is extended as well.
The additional parameter
$decimals
is added and defaults to
null
,
which results in the same behavior as for the TypoScript function
stdWrap.bytes
.
Example
lib.fileSize = TEXT
lib.fileSize {
value = 123456
bytes = 1
bytes.decimals = 1
}
Copied!
Impact
By allowing to configure the number of decimals in
stdWrap.bytes
,
integrators can now better adapt the output of the formatted size returned by
the TypoScript function. This was previously not possible by default and needed
some workarounds in TypoScript.
The TCA configuration config option type=datetime can now specify
the format=datetimesec format to offer a date/time picker for entering
a date (day, month, year) with a specific time (hour, minute, second).
Previously, only a datepicker for hour and minute was available,
even though the utilized component (Flatpickr) supports entering seconds.
This format can either be specified for dbType=datetime (native SQL datetime
columns based on a timestamp that always includes seconds) or
for the integer-based storage without a dbType option (UNIX timestamp).
<?phpreturn [
// ...'columns' => [
'meteor_impact' => [
'label' => 'Time of estimated impact',
'config' => [
'type' => 'datetime',
'format' => 'datetimesec',
'dbType' => 'datetime', // can also be omitted for integer-based storage'nullable' => true, // can also be false
],
],
],
// ...
];
Copied!
Hint
The format datetimesec is only allowed for:
empty
['config']['dbType']
(defaults to integer-based storage type)
['config']['dbType'] = 'datetime'
(native date and time storage type)
Other types (date, time, timesec) do not support both components
and would yield unconsistent data.
Impact
Editors and integrators can now specify dates including seconds
in database record fields, if the fields are configured with the
new TCA config format datetimesec.
This can be set for any TCA field that already internally receives a
UNIX timestamp value.
-- For the editor who has everything, but seconds.
The class
\TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem
is
the central entity to generate various previews of content elements.
Developers can either use the event
\TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent
to generate a preview or implementing
\TYPO3\CMS\Backend\Preview\PreviewRendererInterface
.
The new PSR-14 event
\TYPO3\CMS\Backend\View\Event\AfterPageContentPreviewRenderedEvent
can now be used to enrich the output generated by one of those.
Previously, the date-time picker used the selected locale of a backend user.
However, certain locale configurations might rather be a user preference.
For example, users may prefer an english backend but want to use a weekday
start on "Monday" instead of "Sunday" due to their cultural habits.
Thus, the "first day of a week" has been decoupled from the locale selection
and can be configured on a per-user setting. By default, it still inherits
the locale's default setting, if not changed (for example, english=sunday
and german=monday).
Inside the user settings and tab panel Backend appearance, a new dropdown
First day of week in calendar popups appears.
The setting is stored in both the persisted
be_users.uc
preference blob,
as well as on the JavaScript-persistence side.
Impact
Editors can now choose the first day of a week as a user preference, independent
from locale selection.
Fluid 4.3 introduced the concept of components to Fluid (see
Components). Since then, it
was already possible to use components in TYPO3 projects by creating a custom
ComponentCollection
class that essentially connects a folder of template files
to a Fluid ViewHelper namespace. Using that class it was also possible to use an
alternative folder structure for a component collection and to allow passing
arbitrary arguments to components within that collection.
Now it is possible to define component collections purely with configuration.
For the most common use cases, it is no longer necessary to create a custom
PHP class, which makes it much easier for integrators to setup components in
TYPO3 projects.
Registering component collections
The new extension-level configuration file
Configuration/Fluid/ComponentCollections.php is introduced, which allows
extensions to register one or multiple new component collections. It is also possible
to extend existing collections registered by other extensions (such as adding template
paths to override components defined by another extension).
By default, component collections use a folder structure that requires a
separate folder per component. This is handy if you want to put other
files right next to your component template, such as the matching CSS
or JS file, or even a custom language file. Using the example above,
<my:organism.header.navigation />
would point to
EXT:my_extension/Resources/Private/Components/Organism/Header/Navigation/Navigation.fluid.html.
If not otherwise specified, components use a strict API, meaning that all
arguments that are passed to a component need to be defined with
<f:argument>
in the component template.
Both defaults can be adjusted per collection by providing configuration options:
templateNamePattern allows you to use a different folder structure, available
variables are {path} and {name}. For
<my:organism.header.navigation>
,
{path} would be Organism/Header and {name} would be Navigation.
setting additionalArgumentsAllowed to true allows passing undefined arguments
to components.
Using this example
<my:organism.header.navigation />
would point to
EXT:my_extension/Resources/Private/Components/Organism/Header/Navigation.fluid.html
(note the missing Navigation folder).
The example also demonstrates that components can (and should) use other components, in this
case
<my:atom.icon>
.
Depending on the use case, it might also make sense to pass the output of one component
to another component via a slot:
You can learn more about components in
Defining Components. Note
that this is part of the documentation of Fluid Standalone, which means that it doesn't mention
TYPO3 specifics.
Migration and co-existence with class-based collections
Configuration-based and class-based component collections can be used side by side.
For more advanced use cases, it might still be best to ship a custom class to define
a component collection. However, most use cases can easily be migrated to the
configuration-based approach, since they usually just consist of boilerplate code
around the configuration options.
Since the new approach is not available in TYPO3 13, it is possible to ship both
variants to provide backwards-compatibility: If a specific component collection is
defined both via class and via configuration, in TYPO3 13 the class will be used,
while in TYPO3 14 the configuration will be used and the class will be ignored completely.
Extending component collections from other extensions
It is possible to extend the configuration of other extensions using the
introduced configuration file. This allows integrators to merge their own set of
components into an existing component collection:
For template paths, the familiar rule applies: They will be sorted by their
keys and will be processed in reverse order. In this example, if my_extension
defines a component that already exists in vendor_extension, it will override
the original component in vendor_extension.
Impact
Fluid component collections no longer need to be defined by creating a custom
class, but can now be registered purely by configuration. Existing class-based
collections will continue to work. If a collection namespace is registered both
by a class and by configuration, the configuration overrules the class and any
custom code in the class is ignored.
Feature: #108508 - PSR-14 events for Fluid components
Three PSR-14 events have been added to influence the processing and rendering
of Fluid components that are registered using the new configuration file
(see Fluid components integration).
ModifyComponentDefinitionEvent
The
ModifyComponentDefinitionEvent
can be
used to modify the definition of a component before it's written to the cache.
Component definitions must not have any dependencies on runtime information, as
they might be used for static analysis or IDE auto-completion. Due
to the component definitions cache, this is already enforced, as the registered
events are only executed once and not on every request.
The
ProvideStaticVariablesToComponentEvent
can
be used to inject additional static variables into component templates. As with the
ModifyComponentDefinitionEvent
, these variables
must not have any dependencies on runtime information, as they might be used for
static analysis or IDE auto-completion. The
RenderComponentEvent
can be used to add variables
with runtime dependencies.
Valid use cases for this event might be:
providing static (!) design tokens (colors, icons, ...) to all components in a collection
generating prefix strings based on the component's name
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\EventListener;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Fluid\Event\ProvideStaticVariablesToComponentEvent;
#[AsEventListener]final readonly classProvideStaticVariablesToComponentListener{
publicfunction__invoke(ProvideStaticVariablesToComponentEvent $event): void{
// Provide design tokens to all components in a collectionif ($event->getComponentCollection()->getNamespace() === 'MyVendor\\MyExtension\\Components') {
$event->setStaticVariables([
...$event->getStaticVariables(),
'designTokens' => [
'color1' => '#abcdef',
'color2' => '#123456',
],
]);
}
}
}
Copied!
RenderComponentEvent
The
RenderComponentEvent
can be used to alter or
replace the rendering of Fluid components. There are three possible use cases:
fully take over the rendering of components by filling the
$renderedContent
with
$event->setRenderedContent()
. The first event that does this skips all following
event listeners.
provide additional arguments (= variables in the component template) or slots to
the component with
$event->setArguments()
/
$event->setSlots()
.
execute additional code that doesn't influence the component rendering directly, e. g.
adding certain frontend assets to the page automatically.
The extension-level configuration file Configuration/Fluid/Namespaces.php
is introduced, which enables a structured way to register and extend global
Fluid namespaces. This replaces the old configuration in
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']
, see
deprecation.
TYPO3 reads and merges Configuration/Fluid/Namespaces.php files from all
loaded extensions in the usual loading order, which can be manipulated by
declaring dependencies in composer.json and possibly ext_emconf.php. If an
extension registers a namespace that has already been registered by another
extension, these namespaces will be merged by Fluid. This allows extensions
to override ViewHelpers of another extension selectively.
Namespaces are processed in reverse order, which means that
<myext:demo />
would first check for
EXT:my_extension2/Classes/ViewHelpers/DemoViewHelper.php, and would
fall back to EXT:my_extension1/Classes/ViewHelpers/DemoViewHelper.php.
PSR-14 event to modify namespaces
The new
ModifyNamespacesEvent
is
introduced, which allows modification of the whole namespaces array
before it is being passed to Fluid. This allows for example to:
completely redefine an existing namespace (instead of extending it)
Note that namespaces might still be imported locally from within a
template file, which is unaffected by this event.
Backwards-compatible namespaces in extensions
There are several ways to provide backwards-compatible global namespaces
in extensions, depending on the concrete use case:
preferred: Define namespace in TYPO3_CONF_VARS (with version check) and
in new Namespaces.php. This means that in TYPO3 v14 installations the new
Namespaces.php can already be used to extend the namespace.
alternative: Define namespace both in TYPO3_CONF_VARS (without version
check) and in new Namespaces.php. This means however that the namespace
can only be extended with TYPO3_CONF_VARS, not with the new Namespaces.php.
keep TYPO3_CONF_VARS until support for < v14 is dropped by the extension.
This can only be extended with TYPO3_CONF_VARS as well.
implement own merging logic in
ModifyNamespacesEvent
if necessary.
Namespaces.php can only extend namespaces defined in Namespaces.php.
TYPO3_CONF_VARS can extend both TYPO3_CONF_VARS and Namespaces.php.
ModifyNamespacesEvent
can modify everything.
Impact
Extensions can now register global Fluid namespaces in a dedicated
configuration file Configuration/Fluid/Namespaces.php. The old
TYPO3_CONF_VARS registration can be used for backwards compatibility.
A new default theme has been added. Its main purpose
is to build new sites more rapidly in TYPO3 v14.
The name Camino (spanish "the way") was chosen as v14 development
series, marking the first steps on the way to a glorious future for
TYPO3.
It serves to show that a new site in TYPO3 can be set up within
minutes, being customizable (at least in a limited way), without
having to rely on any external library, and to avoid ANY error
message for newcomers to TYPO3.
Camino will be packaged for new installations by default
and can be activated for new sites alongside existing sites.
The theme shows off basic page structures as well as
some default content elements - completely without any
third-party dependencies nor requiring the "Fluid styled content"
extension.
The rough structure of the theme:
Four different color schemes can be selected in the site settings
The main menu structure and footer structure can be configured
on the root page inside the backend layout / colPos positions
Common content elements for a Hero area and regular content
is available
Minimal configuration is handled in TypoScript
The theme is not meant to evolve within TYPO3, as it will
be moved to TER/Packagist/GitHub in a separate repository
in v15.0. In v15.x a new theme will be added with more
modern features, and it will utilize features that will
be added during v15.x development.
The theme is 100% optional and encapsulated - existing setups
will have no interference.
The theme will be fine-tuned before the TYPO3 v14 LTS release,
specific documentation for its features will be provided in
the theme's documentation.
Installation
For now, the theme is the same as a regular TYPO3 extension.
On fresh classic-mode installations, the theme will be enabled
by default. A new site and a first page will be created, which
can be used to insert content.
On Composer-mode installations, the package typo3/theme-camino
needs to be required, and a fresh installation will also create
the site and a first page.
For existing installations, the theme must be enabled first
(depending on the TYPO3 setup) either via extension manager,
or by requiring the Composer package.
Once the "extension" is activated, the steps to enable
the Camino frontend are:
Create a new root page in the Content > Layout
page tree. Be sure to edit the created page properties and enable
Behavior > Use as Root Page .
This will automatically create a new Site. Check Sites > Setup
to see the created Site. Edit that Site's properties. In
General > Sets for this Site ensure that the Theme: Camino
Site set is added as a dependency.
(Depending on the TYPO3 setup, TYPO3 caches might need to be cleared)
Then edit the created root page properties via Content > Layout again,
and pick Camino: Start page from the tab
Appearance > Backend Layout (this page only).
Now the Camino theme will be applied to the site. Content can be added
in the specific columns, and sub-pages can be created (ensure to set
the backend layout of subpages to the appropriate Camino backend layout,
either Camino: Content page (full-width) or
Camino: Content page (with sidebar).
A custom logo can be set in root page properties Appearance, just
above the backend layout picker.
In Sites > Setup, the Site set configuration can be accessed
to adjust the color scheme and further options.
Impact
A default frontend theme is now available. It can be easily
activated in the TYPO3 installation process, or also be enabled
afterwards.
It is dependency-free and provides and utilizes site sets.
Feature: #108558 - Add original file name to SanitizeFileNameEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\SanitizeFileNameEvent
does now also provide the original file name. Event listeners can use the
original file name to perform custom string replacements (e.g. space to
hyphen instead of underscore) for the sanitized file name.
Impact
It is now possible to retrieve the original file name in the PSR-14 event
\TYPO3\CMS\Core\Resource\Event\SanitizeFileNameEvent
.
Feature: #108623 - Allow content element restrictions per colPos
Backend layouts have been extended with options to allow only
configured types of content elements (referencing
tt_content.CType
with names like "text",
"textmedia", "felogin_pi1" and so on) in backend layout columns (
colPos
): The two
keys
allowedContentTypes
and
disallowedContentTypes
add allow and
deny lists on column level. These settings can be set with Page TSConfig based backend layouts,
Database based backend layouts do not allow configuring this value at the moment, but this will
be added soon.
Example for a backend layout with two rows and two columns configured using Page TSConfig:
mod.web_layout.BackendLayouts {
exampleKey {
title = Example
config {
backend_layout {
colCount = 1
rowCount = 2
rows {
1 {
columns {
1 {
identifier = main
name = Main content
colPos = 0
allowedContentTypes = header, textmedia
}
2 {
identifier = right
name = Panel right
colPos = 1
allowedContentTypes = my_custom_cta
}
}
}
2 {
columns {
1 {
identifier = footer
name = Footer
colpos = 2
colspan = 2
disallowedContentTypes = header
}
}
}
}
}
}
Copied!
The implementation adapts the "New content element wizard" to show only allowed (or not disallowed)
content elements types when adding a content element to a column. When editing records, the select
boxes "Type" and Column position" are reduced to not allow invalid values based on the configuration.
Similar logic is applied when moving and copying content elements.
The feature has been created with extension content_defender
in mind. This extension by Nicole Hummel has been around for many years and found huge adoption rates
within the community. In comparison to content_defender, the core configuration is slightly simplified
and the core implementation does not provide the additional content_defender feature to restrict the number
of elements per column (
maxitems
).
The core implementation supports the content_defender syntax using the arrays
allowed.CType
and
disallowed.CType
. With the example below,
allowed.CType
is internally mapped to
allowedContentTypes
. When both
allowed.CType
and
allowedContentTypes
are given,
allowed.CType
is ignored.
Codewise, the PSR-14 event
ManipulateBackendLayoutColPosConfigurationForPageEvent
has been added. It allows manipulation
of the calculated column configuration. It is marked
@internal
and thus needs to be used with care since it may
change in the future without further note: The event is not dispatched as systematically as it should be, but refactoring
the surrounding code can probably not be provided with TYPO3 v14 anymore. Extensions like ext:container however
must be able to adapt column configuration with TYPO3 v14 already. The decisions was to provide an event, but to mark
it internal for the time being, declaring it as "use at your own risk" if you know what you are doing and within extensions
that set up proper automatic testing to find issues if the core changes internals.
Impact
Backend layout columns can now restrict, which content element types are allowed or disallowed inside of it.
Feature: #108627 - Allow adding inline language domains to JavaScript
The new method
PageRenderer->addInlineLanguageDomain()
allows loading
all labels from a language domain and making them available in JavaScript
via the
TYPO3.lang
object.
The domain name follows the format extension.domain (e.g. core.common,
core.modules.media).
The language file is resolved automatically by the LanguageService, resolving
to files like EXT:core/Resources/Private/Language/locallang_common.xlf
and EXT:core/Resources/Private/Language/Modules/media.xlf.
Labels are automatically prefixed with the domain name and accessible as
TYPO3.lang['domain:key']
, e.g.
TYPO3.lang['core.common:notAvailableAbbreviation']
.
// Access a label from the domainconst label = TYPO3.lang['myextension.frontend:button.submit'];
Copied!
Impact
Previously, it was possible to load entire language files using
addInlineLanguageLabelFile()
(which is still available), but labels
were added without any prefix.
This could lead to naming conflicts when multiple extensions used the same
label keys, potentially overriding each other's translations.
With the new domain-based approach, all labels are automatically prefixed
with the domain name (e.g. myextension.frontend:label.key). This provides
a unified, namespaced access pattern that eliminates the risk of collisions
between labels from different extensions or language files.
Feature: #108663 - Adjust visibility of form elements in Form Editor
The Form Editor now provides the ability to configure the visibility of form
elements. This allows form administrators to control which form elements are
displayed or hidden in the form, providing better control over the form structure
and user experience.
Usage
When editing a form element in the Form Editor, a new "Visibility" option is
available in the element's configuration panel. This option allows you to:
Show the element (default behavior)
Hide the element
The visibility setting is stored in the form definition and is evaluated when
the form is rendered on the frontend.
Example for Integrators
If you want to extend your own form elements with the visibility feature, you
need to add the following configuration to the element definition:
prototypes:standard:formElementsDefinition:CustomElement:formEditor:editors:# Choose a key / position according to your own needs240:identifier:enabledtemplateName:Inspector-CheckboxEditorlabel:formEditor.elements.FormElement.editor.enabled.labelpropertyPath:renderingOptions.enabled
Copied!
Impact
This feature improves the usability of the Form Editor and makes it more accessible
to non-technical users who need to manage form visibility.
Feature: #108694 - Context menu items to edit site configuration for pages
Two new context menu items have been added for pages that are site roots:
Edit Site: Opens the site configuration editor for the site associated
with the page.
Edit Site Settings: Opens the site settings editor for the site.
These items appear directly after the "Edit" item in the context menu and are
only visible to admin users on pages that have a site configuration.
Impact
Admin users can now quickly access the site configuration and site settings
directly from the context menu when right-clicking on a site root page. This
improves the workflow for managing sites without needing to navigate to the
Site Management module first.
Deprecation: #108086 - Raise deprecation error on using deprecated labels
Until now, localization labels marked as deprecated, for example by using the
x-unused-since
attribute in XLIFF 1.2 or the
subState="deprecated"
attribute in XLIFF 2.0, did not trigger a runtime warning. As a result,
integrators and developers had no automatic way to detect deprecated labels
that were still in use.
With this change, TYPO3 now triggers an
E_USER_DEPRECATED
error when a
deprecated label is first written to the localization cache.
Impacted formats
XLIFF 1.2
<trans-unitid="deprecated_label"x-unused-since="4.5"><source>This label is deprecated</source></trans-unit>
Copied!
XLIFF 2.0
<unitid="label5"><segmentsubState="deprecated"><source>This is label #5 (deprecated in English)</source></segment></unit>
Copied!
Custom loaders
When a label identifier ends with .x-unused, TYPO3 raises a deprecation
warning if the label is referenced, regardless of whether the reference includes
the .x-unused suffix.
Custom loaders can use this behaviour to also provide mechanisms to deprecate
labels.
Fallback behaviour
If a label is deprecated in the fallback language but is overridden in the
current locale without a deprecation marker, no deprecation warning is raised.
If a label is deprecated only in the current locale, TYPO3 falls back to the
default language and does not raise a deprecation warning.
A deprecation warning is triggered the first time a deprecated label is written
to cache. Subsequent resolutions of the same label use the cached entry and do
not trigger additional warnings until the cache is cleared.
The following usages emit a deprecation warning when a deprecated label is
resolved.
The Extension Scanner does not detect the usage of deprecated localization
labels. Developers must rely on runtime deprecation logs to identify these
occurrences.
Impact
Integrators and developers may encounter new deprecation warnings during runtime
or in the deprecation log when deprecated localization labels are used. The
warnings help identify and replace outdated labels before they are removed in a
future TYPO3 version.
Affected installations
All TYPO3 installations that use localization labels marked as deprecated are
affected. This includes custom extensions, site packages, or integrations that
still reference deprecated labels from system or extension language files.
When a custom extension or project defines a label whose identifier ends with
.x-unused, that label is considered deprecated regardless of the loader
used. Such usage is technically possible but generally unlikely.
Migration
Review the deprecation log for warnings related to localization labels. Note
that deprecations are only written the first time a label is used after
deleting the cache.
Replace usages of deprecated labels with non-deprecated ones where possible.
If required, override deprecated labels in a custom locale without a
deprecation marker.
Remove or update labels marked with x-unused-since in XLIFF 1.2 or with
subState="deprecated" in XLIFF 2.0 when they are no longer needed.
Avoid defining labels with identifiers ending in .x-unused.
Deprecation: #108524 - Fluid namespaces in TYPO3_CONF_VARS
Registering global namespaces for Fluid templates in TYPO3_CONF_VARS has
been deprecated.
Impact
Defined namespaces in
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']
will no longer be registered in TYPO3 v15.
Affected installations
Installations and extensions that use
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']
to define additional global namespaces or extend existing global namespaces.
Migration
Standard use cases, such as registering a new global namespace or extending
an existing one, can be migrated to the dedicated Configuration/Fluid/Namespaces.php
configuration file.
The exception
\TYPO3\CMS\Core\Console\CommandNameAlreadyInUseException
is unused within TYPO3 Core and has been deprecated.
Impact
Creating a new instance of
\TYPO3\CMS\Core\Console\CommandNameAlreadyInUseException
will trigger a PHP deprecation message.
Affected installations
TYPO3 installations with custom extensions using this exception.
Migration
As the exception is unused in TYPO3 Core, there is no direct replacement.
Extensions relying on this exception should implement their own exception
if needed.
Important: #107971 - XLF files now use 2-space indentation
TYPO3 Core XLF (XLIFF) translation files now consistently use 2-space
indentation instead of tabs. This aligns with the formatting used by Crowdin
and by PHP's
\DOMDocument
. The .editorconfig file has been updated
accordingly.
In addition to indentation, all XLF files have been normalized using a unified
XML formatter. This ensures consistent XML declaration, attribute ordering, and
whitespace structure across all XLF files in the Core.
Checking and normalizing XLF formatting
A new script has been introduced to check and normalize XLF formatting.
Checking formatting via runTests.sh
To check whether XLF files have correct formatting (dry-run, no modifications):
./Build/Scripts/runTests.sh -s normalizeXliff -n
Copied!
This command scans all XLF files in typo3/sysext/ and reports files that
would be changed.
Normalizing XLF files via runTests.sh
To normalize all XLF files in-place:
./Build/Scripts/runTests.sh -s normalizeXliff
Copied!
This command applies consistent indentation and XML normalization to all XLF
files in typo3/sysext/.
Using the standalone script
The script can also be run directly. Requires PHP 8.2+ with DOM and intl
extensions enabled.
# Show help
./Build/Scripts/xliffNormalizer.php --help# Check files only (dry-run)
./Build/Scripts/xliffNormalizer.php --root typo3/sysext --dry-run
# Normalize files in place
./Build/Scripts/xliffNormalizer.php --root typo3/sysext
# Check a custom directory
./Build/Scripts/xliffNormalizer.php --root path/to/xlf/files --dry-run
Copied!
Impact
Extension developers should update their XLF files to use 2-space indentation
and expect normalized XML formatting.
The provided script can also be used within extensions to keep XLF files
consistent with TYPO3 Core standards.
The former page type "External link" has been renamed to "Link" and now fully
supports all typolink capabilities. It now uses the field
link
.
Since the field url was only used by this former page type, it has been
removed.
Impact
Custom code that expects the field url to exist in the table
pages
will fail as the field is not found anymore.
Affected installations
TYPO3 projects that used the former page type External URL to link to
resources other than true external URLs, for example sections such as #abc.
TYPO3 projects with custom code that expects the field url to exist
in the table
pages
.
Migration
The upgrade wizard "Migrate links of pages of type link" automatically migrates
pages of the former type External URL to the new page type Link.
It migrates all links that resolve to external URLs. If your project used the
External URL field for other purposes — for example, to link sections such
as #abc — you must migrate those links manually.
If your project contains custom code that expects the field
url
to exist
in the table
pages
, you can reintroduce this field via a TCA override,
for example:
<?php
defined('TYPO3') || die('Access restricted.');
$GLOBALS['TCA']['pages']['columns']['url'] = [
'label' => 'External URL',
'config' => [
'type' => 'input',
'size' => 50,
'max' => 255,
'required' => true,
'eval' => 'trim',
'softref' => 'url',
'behaviour' => [
'allowLanguageSynchronization' => true,
],
],
];
// Adding column to a existing or new palette and configure showitem,
// for a specific doktype the field is still required, for example:
$GLOBALS['TCA']['pages']['palettes]['custom_url'] = [
'showitem' => 'url',
];
$GLOBALS['TCA']['pages']['types'][$customDokTypeValue] = [
'showitem' => [
--div--;core.form.tabs:general,
doktype,
--palette--;;title,
--palette--;;custom_url,
],
];
Copied!
Adding it back at least ensures that the database field is not renamed and
dropped by the Database Analyzer. In case TCA is not required while keeping
the database field it could be added to a extension ext_tables.sql file.
The
\TYPO3\CMS\Core\Collection\SortableCollectionInterface
has been
removed from the TYPO3 Core.
This interface was never properly implemented and served no purpose in the
codebase. It defined methods for sorting collections via callback functions
and moving items within collections, but no concrete implementations existed.
The interface defined the following methods:
usort($callbackFunction)
– for sorting a collection via a given callback function
moveItemAt($currentPosition, $newPosition = 0)
– for moving items within the collection
Impact
Any code that implements or references
SortableCollectionInterface
will trigger a PHP fatal error.
Since this interface was never implemented in the TYPO3 Core and had no real-world usage,
the impact should be minimal for most installations.
Affected installations
Installations with custom extensions that implement or reference the
SortableCollectionInterface
are affected.
Migration
Remove any references to
SortableCollectionInterface
from your code.
If you need sortable collection functionality, implement your own sorting logic
directly in your collection classes, or use PHP's built-in array sorting functions
such as
usort()
,
uasort()
, or
uksort()
.
Breaking: #68303 - Make tt_content imagewidth/imageheight nullable
The Page Module preview rendering has been refactored to use the Record API
internally instead of accessing raw database arrays. This affects both custom
preview renderers that extend
StandardContentPreviewRenderer
and
Fluid-based preview templates.
The method signature has changed for
\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer
:
StandardContentPreviewRenderer->linkEditContent()
now expects a
RecordInterface
object as the second
$record
parameter
instead of an array
The
\TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent
has
also been updated:
PageContentPreviewRenderingEvent->getRecord()
now returns a
RecordInterface
object instead of an array
PageContentPreviewRenderingEvent->setRecord()
now expects a
RecordInterface
object instead of an array
Additionally, the @internal class
\TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem
has been
updated to work with Record objects:
The constructor of
GridColumnItem
now requires a
RecordInterface
object as
the third
$record
parameter instead of an array
GridColumnItem->getRecord()
now returns a
RecordInterface
object
instead of an array
GridColumnItem->setRecord()
now expects a
RecordInterface
object
instead of an array
A new method
GridColumnItem->getRow()
has been added to access the raw
database array if needed
For Fluid-based content element previews, the template variables have changed.
Previously, all record fields were passed as individual variables to the Fluid
template. Now, only a single
{record}
variable is passed, which is a
RecordInterface
object providing access to all record data through
the Record API.
Using the
{pi_flexform_transformed}
variable in Fluid-based content
element previews no longer works. The resolved flex form can be directly
accessed on the
RecordInterface
object, for example via
{record.pi_flexform}
. The value is a
FlexFormFieldValues
object, which properly groups the fields by their sheets.
Impact
Extensions that extend
StandardContentPreviewRenderer
and override the
linkEditContent()
method will need to update their method signature.
Extensions that access
GridColumnItem->getRecord()
expecting an array will
need to update their code to work with
RecordInterface
objects.
Extensions using event listeners for
PageContentPreviewRenderingEvent
that
access the record via
getRecord()
expecting an array will need to update
their code to work with
RecordInterface
objects.
Custom Fluid templates for content element preview rendering must be updated to
use the
{record}
variable instead of accessing individual field variables.
Affected installations
All installations with extensions that:
Extend
StandardContentPreviewRenderer
and call or override the
linkEditContent()
method
Instantiate
GridColumnItem
or call
GridColumnItem->getRecord()
/
GridColumnItem->setRecord()
Register event listeners for
PageContentPreviewRenderingEvent
Use custom Fluid templates for content element preview rendering via PageTSconfig
mod.web_layout.tt_content.preview.[recordType]
Migration
For custom preview renderers extending
StandardContentPreviewRenderer
:
Update the method signature of
linkEditContent()
to accept a
RecordInterface
object:
<f:variablename="path"value="s_messages/settings" /><small>{record.pi_flexform.{path}.welcome_header}</small><!-- or --><small>{record.pi_flexform.sheets.s_messages.settings.welcome_header}</small>
The backend submodule Database Relations within DB Check provided
information about potentially broken database relations. However, the
information it displayed was very limited and barely helpful. In addition, the
entire module and its code have not received any meaningful updates in recent
years.
Due to this, the module has been removed.
Impact
The module has been removed. Existing links and stored bookmarks will no longer
work.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished']
has been removed in favor of the more powerful PSR-14 events
\TYPO3\CMS\Form\Event\BeforeRenderableIsAddedToFormEvent
and
\TYPO3\CMS\Form\Event\AfterFormIsBuiltEvent
.
Impact
Any hook implementation registered under this identifier will no longer be
executed in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions that implement this hook are
affected. The extension scanner reports such usages as a weak match.
Migration
The hook has been removed without a deprecation phase to allow extensions to
remain compatible with both TYPO3 v13 (using the hook) and v14+ (using the new
events). Implementing the PSR-14 events provides the same or greater control
over form rendering.
Extbase's
PropertyMappingConfigurationInterface
is now fully typed with native PHP types.
Impact
Existing implementations will no longer work without adjustment. According to
the Liskov Substitution Principle, all implementations must follow the updated
method signatures and type restrictions defined in the interface.
Affected installations
TYPO3 installations with custom PHP code implementing a custom
PropertyMappingConfiguration
are
affected. Such cases are rare.
Migration
Add the required native PHP types to all custom implementations of the
PropertyMappingConfigurationInterface
to fulfill the updated interface definition.
Breaking: #101392 - getIdentifier() and setIdentifier() from AbstractFile removed
When using the PHP API of the File Abstraction Layer (FAL), several classes
are involved in representing file objects.
In addition to the
FileInterface
, there is also the
AbstractFile
class, from which most file-
related classes inherit.
To ensure stricter type consistency, the abstract class no longer implements
the methods
getIdentifier()
and
setIdentifier()
. Implementing
these methods is now the responsibility of each subclass.
The methods are now implemented in the respective concrete classes inheriting
from
AbstractFile
.
Impact
In the unlikely case that the TYPO3 File Abstraction Layer has been extended
with custom PHP classes derived from
AbstractFile
, this change will
cause a fatal PHP error, as the new abstract methods
getIdentifier()
and
setIdentifier()
must be implemented by the subclass.
Affected installations
TYPO3 installations that include custom code extending the File Abstraction
Layer are affected. Such cases are considered highly uncommon.
Migration
Implement the two methods
getIdentifier()
and
setIdentifier()
in
any custom file class extending
AbstractFile
.
This can also be implemented in older TYPO3 versions to ensure forward
compatibility with TYPO3 v14 and later.
Breaking: #103141 - Use doctrine GUID type for TCA type=uuid
The TYPO3 Doctrine implementation can now handle the proper
matching database type for UUID's.
Postgresql natively supports the UUID data type and is way faster
than the prior VARCHAR(36) generated from the string type.
The Doctrine DBAL GUID type uses CHAR(36) as the fixed field column size
for non-postgres databases, which is compatible as long
as valid UUID values were persisted in the configured database
table.
Impact
TYPO3 database tables can now natively properly apply the suitable
GUID column type when configured as TCA type=uuid.
A prerequisite for this is that you only have valid UUID's stored
in the database table, otherwise the database update will report
an error when applying migrations.
Affected installations
Projects with database table columns set as TCA type=uuid.
Error are likely to occur, if invalid UUID data is stored
in field columns configured with this type.
Migration
Use the database analyzer to migrate the database fields.
Invalid values are not migrated and need to be manually
cleaned up in affected instances.
Breaking: #103910 - Change logout handling in EXT:felogin
The logout handling has been adjusted to correctly dispatch the PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\LogoutConfirmedEvent
when a logout
redirect is configured. The
actionUri
variable has been removed, and the
logout template has been updated to reflect this change, including correct use
of the
noredirect
functionality.
Impact
The PSR-14 event
LogoutConfirmedEvent
is now
correctly dispatched when a logout redirect is configured. Additionally, the
noredirect
parameter is now evaluated during logout.
Affected installations
TYPO3 installations using EXT:felogin with a custom Fluid template for
the logout form.
Migration
The
{actionUri}
variable is no longer available and must be removed
from custom templates.
Before:
Fluid template adjustment (before)
<!-- Before --><f:formaction="login"actionUri="{actionUri}"target="_top"fieldNamePrefix="">
Redirect handling for the
logoutAction
has been removed.
Impact
The
logoutAction
no longer performs any configured redirect via plugin
settings or GET parameters.
Affected installations
TYPO3 installations relying on redirect handling within
logoutAction
are affected.
Migration
No migration is required. The previous redirect logic in
logoutAction()
has been removed because it was incorrect: it ignored
the
showLogoutFormAfterLogin
setting and could trigger an unintended
redirect even when this option was enabled.
Valid redirects are already processed correctly by
loginAction()
and
overviewAction()
, so the faulty branch was
removed without replacement.
Breaking: #104422 - Move GET parameters in sitemap into namespace
The names of the GET parameters used in the sitemap generated by EXT:seo have
been changed from page and sitemap to tx_seo[page] and tx_seo[sitemap],
respectively.
Impact
Applications, routing configurations, and third-party tools that rely on the
parameters being named page and sitemap will break.
Affected installations
This affects installations that use the arguments page and sitemap or
override the sitemap templates of EXT:seo.
Migration
If the arguments are mapped in the routing configuration, the code needs to
be slightly adapted. A working example:
If the templates in EXT:seo/Resources/Private/Templates/XmlSitemap/Index.xml
have been modified, adjust the generated links to match the original ones.
If the URL to a single sitemap has been provided to a third-party tool such as
a crawler or search engine, it must be re-added using the new URL.
The following methods changed signature according to previous deprecations in v13 at the end of the argument list:
\TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->__construct()
- All arguments are now mandatory (Deprecation entry)
\TYPO3\CMS\Core\Imaging\IconFactory->getIcon()
(argument 4 is now of type
\TYPO3\CMS\Core\Imaging\IconState|null
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\AbstractFile->copyTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\AbstractFile->moveTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\AbstractFile->rename()
(argument 2 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\FileInterface->rename()
(argument 2 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\FileReference->rename()
(argument 2 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\Folder->addFile()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\Folder->addUploadedFile()
(argument 2 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\Folder->copyTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\Folder->moveTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\InaccessibleFolder->addFile()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\InaccessibleFolder->addUploadedFile()
(argument 2 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\InaccessibleFolder->copyTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\InaccessibleFolder->moveTo()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->addFile()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->addUploadedFile()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->copyFile()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->copyFolder()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->moveFile()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->moveFolder()
(argument 4 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Resource\ResourceStorage->renameFile()
(argument 3 is now of type
\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior
) (Deprecation entry)
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin()
(argument 2
$type
and 3
$extensionKey
have been dropped) (Deprecation entry)
The following public class properties have been dropped:
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['driver-middleware-identifier']
must be an array, not a class string (Deprecation entry)
Accepting arrays returned by
readFileContent()
in Indexed Search external parsers (Deprecation entry)
Allowing instantiation of
\TYPO3\CMS\Core\Imaging\IconRegistry
in ext_localconf.php (Deprecation entry)
Accepting a comma-separated list of fields as value for the columnsOnly parameter (Deprecation entry)
Support for extbase repository magic
findByX()
,
findOneByX()
and
countByX()
methods (Deprecation entry)
Fluid view helpers that extend
\TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper
should no longer register
class
attribute and should rely on attribute auto registration
for the error class to be added correctly. (Deprecation entry)
The legacy backend entry point typo3/index.php has been removed along with handling of composer.json
setting extra.typo3/cms.install-deprecated-typo3-index-php(Deprecation entry)
The following upgrade wizards have been removed:
Install extension "fe_login_mode" from TER
Migrate base and path to the new identifier property of the "sys_filemounts" table
Migrate site settings to separate file
Set workspace records in table "sys_template" to deleted
Migrate backend user and groups to new module names
Migrate backend groups "explicit_allowdeny" field to simplified format
Migrate sys_log entries to a JSON formatted value
Migrate storage and folder to the new folder_identifier property of the "sys_file_collection" table
The following JavaScript method behaviours have changed:
FormEngineValidation.markFieldAsChanged()
always requires
HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement
to be passed as first argument (Deprecation entry)
FormEngineValidation.validateField()
always requires
HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement
to be passed as first argument (Deprecation entry)
The following JavaScript method has been removed:
updateQueryStringParameter()
of
@typo3/backend/utility.js
(Deprecation entry)
The following smooth migration for JavaScript modules have been removed:
@typo3/backend/page-tree/page-tree-element
to
@typo3/backend/tree/page-tree-element
(Deprecation entry)
The following localization XLIFF files have been removed:
The
DataHandler
PHP API has been
extended to support both qualified and unqualified ISO8601 date formats, in
order to correctly process supplied timezone offsets when provided.
Qualified ISO8601: Includes an explicit timezone offset (for example,
1999-12-11T10:09:00+01:00 or 1999-12-11T10:09:00Z)
Unqualified ISO8601: Omits timezone offsets, representing LOCALTIME
(for example, 1999-12-11T10:09:00)
Previously, TYPO3 incorrectly used qualified ISO8601 with Z (UTC+00:00) to
denote LOCALTIME and applied the server's timezone offset, which led to
misinterpretations when another timezone offset was provided, or when real
UTC-0 was intended instead of LOCALTIME. Now, timezone offsets are accurately
applied if supplied, and are based on server localtime if omitted.
TYPO3 will use unqualified ISO8601 dates internally for communication between
FormEngine and the DataHandler API, ensuring timezone offsets are correctly
processed – instead of being shifted – when supplied to the
DataHandler
API.
In essence, this means that existing workarounds for previously applied
timezone offsets should be reviewed and removed.
Impact
TYPO3 now provides accurate and consistent handling of ISO8601 dates,
eliminating previous issues related to timezone interpretation and LOCALTIME
representation.
Affected installations
Installations with custom TYPO3 extensions that invoke the
DataHandler
API with data for
type="datetime" fields are affected.
Migration
Qualified ISO8601 dates with intended timezone offsets and
\DateTimeInterface
objects can now be passed directly to the
DataHandler
without requiring manual
timezone adjustments.
An example of a previous workaround that added timezone offsets for the
DataHandler:
Passing datetime data via DataHandler PHP API (before)
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
'tx_myextension_mytable' => [
'NEW-1' => [
'pid' => 2,
// A previous workaround added the localtime offset to supplied// dates, as it was subtracted by the DataHandler persistence// layer'mydatefield_1' => gmdate('c', $myDate->getTimestamp() + (int)date('Z')),
],
],
]);
Copied!
Previous timezone-shifting workarounds can be removed and replaced with more
intuitive formats.
Passing datetime data via DataHandler PHP API (after)
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
'tx_myextension_mytable' => [
'NEW-1' => [
'pid' => 2,
// Pass \DateTimeInterface object directly'mydatefield_1' => $myDate,
// Format as LOCALTIME'mydatefield_2' => $myDate->format('Y-m-d\TH:i:s'),
// Format with timezone information// (offsets will be normalized to the persistence timezone// format: UTC for integer fields, LOCALTIME for native// DATETIME fields)'mydatefield_3' => $myDate->format('c'),
],
],
]);
Copied!
Breaking: #105686 - Avoid obsolete $charset in sanitizeFileName()
Implementing classes no longer need to handle a second argument.
Impact
This change has little to no impact, since the main API caller - the Core
class
ResourceStorage
- never passed a
second argument. The default implementation,
LocalDriver
, has therefore always
behaved as if handling UTF-8 strings.
Affected installations
TYPO3 installations with custom File Abstraction Layer (FAL) drivers
implementing
DriverInterface
may
be affected.
Migration
Implementing classes should drop support for the second argument. Retaining it
does not cause a conflict with the interface, but the TYPO3 Core will never
call
sanitizeFileName()
with a second parameter.
The following methods have been removed from
\TYPO3\CMS\Core\Charset\CharsetConverter
:
CharsetConverter->conv()
CharsetConverter->utf8_encode()
CharsetConverter->utf8_decode()
CharsetConverter->specCharsToASCII()
, use
CharsetConverter->utf8_char_mapping()
instead
CharsetConverter->sb_char_mapping()
CharsetConverter->euc_char_mapping()
This removes most helper methods that implemented conversions between different
character sets from the TYPO3 Core. The vast majority of websites now use UTF-8
and no longer require the expensive charset conversions previously provided by
the Core framework.
Impact
Calling any of the removed methods will trigger a fatal PHP error.
Affected installations
The TYPO3 Core has not exposed any of this low-level functionality in upper
layers such as TypoScript for quite some time. The removal should therefore
have little to no impact on most installations.
The only cases that may be affected are import or export extensions that
perform conversions between legacy character sets (for example, those in the
EUC family). Affected extensions can mitigate this change by copying the
TYPO3 v13 version of the class
CharsetConverter
, including the relevant
files from core/Resources/Private/Charsets/csconvtbl/, into their own
codebase.
The extension scanner will detect usages and classify them as weak matches.
Migration
Avoid calling any of the removed methods. Extensions that still require this
functionality should copy the necessary logic into their own codebase or use a
third-party library.
This particular case has a direct substitution:
// Before
$charsetConverter->specCharsToASCII('utf-8', $myString);
// After
$charsetConverter->utf8_char_mapping($myString);
Copied!
Breaking: #105728 - Extbase backend modules not in page context rely on global TypoScript only
Configuration of Extbase-based backend modules can be done using frontend
TypoScript.
The standard prefix in TypoScript to do this is
module.tx_myextension
. Extbase backend module controllers can
typically retrieve their configuration using a call like:
$configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'myextension');
TypoScript itself is always bound to a page: The frontend must have
either some rootline page with a sys_template record or a page that has a site
set, otherwise frontend rendering will terminate with an error message.
Extbase-based backend modules are sometimes bound to pages as well: They can
have a rendered page tree configured in their module configuration and then
receive the selected page UID within the request as GET parameter
id
.
Other Extbase-based backend modules, however, are not inside a page scope and do
not render the page tree. Examples of such modules within the TYPO3 Core are the
backend modules delivered by the form and beuser extensions.
Such Extbase-based backend modules without a page tree had a hard time
calculating their relevant frontend TypoScript-based configuration: Since
TypoScript is bound to pages, they looked for "the first" valid page in the page
tree, and the first valid sys_template record to calculate their TypoScript
configuration. This dependency on guesswork made final configuration of Extbase
backend module configuration not in page context brittle, opaque, and clumsy.
TYPO3 v14 puts an end to this: Extbase backend modules without page context
compile their TypoScript configuration from global TypoScript only and no longer
calculating TypoScript by guessing "the first valid" page.
The key call to register such "global" TypoScript is the method
ExtensionManagementUtility::addTypoScriptSetup()
in
ext_localconf.php files.
Impact
Configuration of Extbase-based backend modules may change if their configuration
is defined by the first valid page in the page tree. Configuration of such
backend modules can no longer be changed by including TypoScript on the "first
valid" page.
Affected installations
Instances with Extbase-based backend modules without a page tree may be affected.
Migration
Configuration of Extbase-based backend modules without a page tree must be
supplied programmatically and made "global" by extending
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup']
using
ExtensionManagementUtility::addTypoScriptSetup()
within extensions’
ext_localconf.php files. The backend module of the form extension is a
good example. Additional locations of extensions that deliver form YAML
definitions are defined like this:
Note it is also possible to use the method
ExtensionManagementUtility::addTypoScriptConstants()
to declare "global"
TypoScript constants and to use them in the TypoScript shown above.
Breaking: #105733 - FileNameValidator no longer accepts custom regex in __construct()
The class
FileNameValidator
no
longer accepts a custom file deny pattern in
__construct()
. The service
is now stateless and can be injected without side effects.
Impact
A custom partial regex passed as the first constructor argument when
instantiating the service is now ignored. The service relies on
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern']
configuration and a
hard-coded constant as a fallback.
Affected installations
Instances with custom extensions using
GeneralUtility::makeInstance(FileNameValidator::class, 'some-custom-pattern');
are affected. This is expected to be a very rare case.
Migration
Extensions that need to be tested with custom patterns that cannot be declared
globally using
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern']
should implement their own service for this purpose or inline the necessary
code. The core implementation performing the check is only a few lines long.
The event
\TYPO3\CMS\Core\Mail\Event\AfterMailerInitializationEvent
has been removed. This event became obsolete with the introduction of the
Symfony-based mailer in TYPO3 v10. It was only able to influence the TYPO3 Core
mailer by calling the
@internal
method
injectMailSettings()
after
the settings had already been determined. The event has been removed since it
no longer had a meaningful use case.
Impact
Event listeners registered for this event will no longer be triggered.
Affected installations
This event has had little purpose since the switch to the Symfony-based mailer
and is probably not used in most instances. The extension scanner will find
usages.
Migration
Check if this event can be substituted by reconfiguring
$GLOBALS['TYPO3_CONF_VARS']['MAIL']
, or by listening for the event
BeforeMailerSentMessageEvent
instead.
Breaking: #105855 - Remove file backwards compatibility for alt and link field
When the File Abstraction Layer (FAL) was introduced, the TYPO3 Core file fields
media for table pages, and image and assets for table
tt_content, had their so-called "overlay palettes" overridden to
imageOverlayPalette, so that additional fields like alternative, link, and
crop were displayed. However, this was done for all file types, including
text, application, and the fallback type unknown. For these types, the
additional fields served no meaningful purpose. For this reason, they have now
been removed.
Impact
The TYPO3 Core file fields media for table pages, image and
assets for table tt_content, will no longer display the fields
alternative and link for file types other than image.
Affected installations
This affects installations that use one of the Core fields for file types
other than image (for example text or application) and make use of the
fields alternative and/or link.
This should not affect many installations, as these fields are used primarily
for images.
Migration
These fields can be restored using TCA overrides if necessary. First,
register a new palette for the sys_file_reference table containing the desired
set of fields.
Then, use this palette for your specific Core field and file type. The following
example restores the fields alternative and link for the media field of
the pages table when the file type is text.
The TypoScript setting
exposeNonexistentUserInForgotPasswordDialog
has been removed in
EXT:felogin.
Impact
Using the TypoScript setting
exposeNonexistentUserInForgotPasswordDialog
has no effect anymore. The
password recovery process in EXT:felogin now always shows the same message
when a username or email address is submitted in the password recovery form.
Affected installations
Websites using the TypoScript setting
exposeNonexistentUserInForgotPasswordDialog
in EXT:felogin are affected.
Migration
The setting has been removed without replacement. It is possible to use the
PSR-14 event
SendRecoveryEmailEvent
to implement
similar functionality if absolutely necessary. From a security perspective,
however, it is strongly recommended not to expose the existence of email
addresses or usernames.
An exception handling detail within the File Abstraction Layer (FAL) resource
handling has been changed. When calling
getSubFolder('mySubFolderName')
on a
Folder
object, and if this subfolder does
not exist, the specific
\TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
is
now raised instead of the global
\InvalidArgumentException
.
Impact
The change may affect extensions that directly or indirectly call
Folder->getSubFolder()
and expect a
\InvalidArgumentException
to
be thrown.
Affected installations
FolderDoesNotExistException
does not extend
\InvalidArgumentException
. Code that currently expects a
\InvalidArgumentException
to be thrown needs to be adapted.
Migration
The change is breaking for code that takes an "optimistic" approach like:
"get the subfolder object, and if this throws, create one". Example:
Extensions that need to stay compatible with both TYPO3 v13 and v14 should catch
both exceptions and should later avoid catching
\InvalidArgumentException
when v13 compatibility is dropped:
useTYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
try {
$mySubFolder = $myFolder->getSubFolder('mySubFolder');
} catch (\InvalidArgumentException|FolderDoesNotExistException) {
// @todo: Remove \InvalidArgumentException from catch list when// TYPO3 v13 compatibility is dropped.
$mySubFolder = $myFolder->createFolder('mySubFolder');
}
Extbase previously supported the TypoScript toggle
config.tx_extbase.persistence.updateReferenceIndex
to control
whether the reference index should be updated when records are persisted.
It has become increasingly important that the reference index is always kept
up to date, since an increasing number of TYPO3 Core components rely on current
reference index data. Using the reference index at key points can improve read
and rendering performance significantly.
This toggle has been removed. Reference index updating is now always enabled.
Impact
The change may slightly increase database load, which can become noticeable
when Extbase updates many records at once.
Affected installations
Instances with extensions that write many records using the Extbase persistence
layer may be affected.
Migration
The TypoScript toggle
config.tx_extbase.persistence.updateReferenceIndex
should be
removed from any extension codebase, as it is now ignored by Extbase.
Breaking: #106056 - Add setRequest and getRequest to Extbase ValidatorInterface
Custom validators implementing
\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface
must now
also implement the methods
setRequest()
and
getRequest()
.
Impact
Missing implementations of the methods
setRequest()
and
getRequest()
will now result in a PHP fatal error.
Affected installations
TYPO3 installations with custom extensions implementing
ValidatorInterface
.
Migration
The methods
setRequest()
and
getRequest()
must be implemented in
affected validators.
If there is no need to directly implement
ValidatorInterface
, it is
recommended to extend
AbstractValidator
, where
both methods are already implemented.
The public property
\TYPO3\CMS\Core\DataHandling\DataHandler->storeLogMessages
has
been removed without substitution. It should no longer be used by extensions.
Impact
Setting or reading the property in an extension will now raise a PHP warning-
level error.
Affected installations
Instances with extensions that access this property are affected. This should
be a very rare use case. No TYPO3 Extension Repository (TER) extensions were
affected during verification. The extension scanner is configured to find
usages as a weak match.
Migration
The property has been removed. Any code setting or reading it from
\TYPO3\CMS\Core\DataHandling\DataHandler
instances should be removed. The
DataHandler->log()
method now always writes the given
$details
to the
sys_log
table.
Breaking: #106307 - Use stronger cryptographic algorithm for HMAC
TYPO3 now uses SHA3-256 for HMAC operations across multiple components,
replacing the previously used MD5, SHA1, and SHA256 algorithms. SHA3-256
(Keccak) produces 64-character hexadecimal hashes compared to the 32 or
40 characters of the older algorithms.
The following components have been upgraded:
Component
Previous HMAC
New HMAC
cHash
MD5
SHA3-256
Backend password recovery
SHA1
SHA3-256
Frontend password recovery
SHA1
SHA3-256
File dump controller
SHA1
SHA3-256
Show image controller
SHA1
SHA3-256
Backend form protection
SHA1
SHA3-256
Extbase form request attributes
SHA1
SHA3-256
Form extension request attributes
SHA1
SHA3-256
Database session backend
SHA256
SHA3-256
Redis session backend
SHA256
SHA3-256
Database fields have been extended to accommodate the longer hash values
(and would even support SHA3-512 with 128 hexadecimal characters in the
future):
Backend user password reset links in progress become invalid
Frontend user password reset links in progress become invalid
Users must request new password reset emails
Session handling:
Existing session identifiers will be regenerated on next user login
No immediate session invalidation occurs
Database schema:
Field lengths are automatically updated during upgrade
No data migration is required for existing records
Affected installations
All installations upgrading to TYPO3 v14 are affected.
The impact varies based on usage:
High impact: installations with active password reset processes or
cached frontend URLs with cHash parameters
Medium impact: installations using file dump or show image
controllers with externally stored URLs
Low impact: all other installations (automatic migration on next
use)
Migration
Database schema updates:
Execute the database analyzer in the Install Tool or run
vendor/bin/typo3 upgrade:run
.
URLs and caching:
Frontend caches should be cleared to regenerate cHash values
File dump and show image URLs regenerate automatically on next access
External references to file or image URLs must be updated
Important
Existing links with &cHash= URL parameters will become invalid and
respond with an HTTP 404 error. Search engines first need to crawl the
site and discover the new URLs that contain the longer cache hash
value. This likely has an impact on SEO.
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['fallbackToLegacyHash'] = true;
can be used to still allow the legacy MD5 cache hash during
frontend requests.
Sessions:
No manual intervention is required. Sessions are automatically rehashed on
next login.
Custom extensions:
If custom code uses
HashService::hmac()
directly, review whether the
default SHA1 algorithm is still appropriate. Consider explicitly passing
HashAlgo::SHA3_256
for new HMAC operations:
To enable dependency injection for TypolinkBuilder classes, several breaking
changes were introduced to the TypolinkBuilder architecture.
The following breaking changes have been made:
The constructor of
AbstractTypolinkBuilder
has been
removed. Extending classes can no longer rely on receiving
ContentObjectRenderer
and
TypoScriptFrontendController
through the constructor.
All concrete TypolinkBuilder implementations now implement the new
TypolinkBuilderInterface
and use
dependency injection via their constructors instead of extending
AbstractTypolinkBuilder
with
constructor arguments.
The method signature of the main link-building method has changed from
build(array &$linkDetails, string $linkText, string $target, array $conf)
to
buildLink(array $linkDetails, array $configuration, ServerRequestInterface $request, string $linkText = '')
.
Impact
Custom TypolinkBuilder implementations extending
AbstractTypolinkBuilder
will fail
with fatal errors due to the removed constructor and changed method signatures.
Extensions that instantiate TypolinkBuilder classes directly will also fail,
as the constructor signatures have fundamentally changed to use dependency
injection.
Directly instantiate TypolinkBuilder classes in PHP code
Override or extend the
build()
method of TypolinkBuilder classes
Migration
For custom TypolinkBuilder implementations:
Implement
TypolinkBuilderInterface
Use dependency injection in the constructor for required services
Replace the
build()
method with
buildLink()
Note: Classes implementing
TypolinkBuilderInterface
are
automatically configured as public services in the dependency injection
container - no manual configuration is required.
Example migration:
Before (TYPO3 v13 and lower)
useTYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
classMyCustomLinkBuilderextendsAbstractTypolinkBuilder{
publicfunctionbuild(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface{
// Custom link building logicreturnnew LinkResult('news', $linkText);
}
}
For code that instantiates TypolinkBuilder classes directly:
It is strongly recommended to use the
LinkFactory
instead of instantiating
TypolinkBuilder classes directly. The
LinkFactory
handles proper
instantiation and dependency injection automatically.
Breaking: #106412 - TCA interface settings for list view removed
Each TCA definition previously had an optional section named
['interface']
, which defined parameters for displaying TCA records.
The last remaining options within this section,
$GLOBALS['TCA'][$table]['interface']['maxSingleDBListItems']
and
$GLOBALS['TCA'][$table]['interface']['maxDBListItems']
, have been
removed. As a result, the entire
['interface']
section is no longer
supported and will be ignored.
These settings were used to define the number of table rows displayed within
the Content > List backend module.
Impact
The
$GLOBALS['TCA'][$table]['interface']
section in TCA definitions is
no longer evaluated.
Setting any values under this key in custom extensions has no effect and will
be automatically removed during build time.
Affected installations
TYPO3 installations with custom TCA settings defining
$GLOBALS['TCA'][$table]['interface']
are affected.
Migration
Visual display settings can still be overridden on a per-user or per-page
basis via TSconfig. This approach is more flexible, as it allows rendering
different numbers of items per site, page tree, or user group.
The TCA option
$GLOBALS['TCA'][$table]['interface']['maxSingleDBListItems']
has been
removed in favor of
mod.web_list.itemsLimitSingleTable
.
The TCA option
$GLOBALS['TCA'][$table]['interface']['maxDBListItems']
has been removed
in favor of
mod.web_list.itemsLimitPerTable
.
Breaking: #106427 - File Abstraction Layer related changes
In TYPO3 v14, the PHP API of the File Abstraction Layer (FAL) has undergone
major internal changes that may affect extension authors.
Most PHP classes and interfaces within FAL are now strongly typed using
native PHP type declarations.
Several methods have been moved from
AbstractFile
to its concrete
implementation
File
, since only this
class should provide such behavior. The moved methods are:
AbstractFile->rename()
AbstractFile->copyTo()
AbstractFile->moveTo()
The
FileInterface
method
rename()
has been removed, as this operation is only relevant to the
concrete
File
implementation.
Classes implementing
FolderInterface
must now implement the following methods to stay compatible with the new
Folder
class:
getSubFolder()
getReadablePath()
getFiles()
searchFiles()
Previously, this was not enforced, but it is now required to make
Folder
more interchangeable across implementations.
Impact
Calling PHP classes or methods from the File Abstraction Layer directly may
result in fatal PHP errors if incorrect or missing argument types are used.
Affected installations
TYPO3 installations with third-party extensions that interact with the FAL PHP
API in non-documented ways.
Migration
Ensure that all FAL-related code passes and expects the correct PHP types when
using or extending the API.
If you provide custom implementations of
FolderInterface
or classes extending
AbstractFile
, make sure to implement the
newly required methods accordingly.
Breaking: #106503 - Removal of fields from sys_file_metadata
The following database fields and corresponding TCA definitions have been
removed from the database table
sys_file_metadata
without substitution:
visible
fe_groups
These fields were added to the table when the system extension
EXT:filemetadata was installed.
Although the field names suggested access control functionality similar to
those found in other TYPO3 tables, they were never configured to restrict
frontend or backend access. Their intended meaning would have depended on
custom implementation and was not supported by the TYPO3 Core.
Additionally, the field
status
has been moved from the Access tab to
the Metadata tab to avoid the impression that this field has any restrictive
behavior.
Impact
The fields
visible
and
fe_groups
in
sys_file_metadata
are
no longer used. After performing a Database Compare in the Install Tool,
these columns will be removed and their data permanently lost.
Accessing these fields in PHP or TypoScript will result in PHP warnings.
Affected installations
Any TYPO3 installation using the fields
visible
or
fe_groups
provided by the system extension EXT:filemetadata is affected.
Migration
No automatic migration is available.
If the fields are still required for custom logic, reintroduce their database
columns and TCA configuration within a custom extension.
In earlier TYPO3 versions, the Form Framework provided two template variants
for frontend rendering:
The initial, legacy templates in
EXT:form/Resources/Private/Frontend/Templates and
EXT:form/Resources/Private/Frontend/Partials, which were deprecated
in forge#95456
The newer, Bootstrap 5 compatible and accessible templates in
EXT:form/Resources/Private/FrontendVersion2/Templates and
EXT:form/Resources/Private/FrontendVersion2/Partials, introduced
with forge#94868
The legacy form templates have now been removed. The rendering option
templateVariant
, which toggled the template and form configuration
variant, has been removed as well.
The newer template variants have been moved to the original file paths of the
legacy templates.
Impact
The removal of the legacy templates and the
templateVariant
configuration option simplifies the Form Framework rendering logic.
Developers no longer need to choose between multiple template variants,
reducing complexity and improving maintainability. Projects already using the
newer templates benefit from a cleaner configuration and a unified rendering
approach.
Affected installations
All TYPO3 installations using the Form Framework are affected.
Migration
If you still rely on the legacy templates, you must migrate your templates and
partials to the structure of the newer templates.
Websites that use
templateVariant: version2
can simplify their form
configuration. Variants with the condition
'getRootFormProperty("renderingOptions.templateVariant") == "version2"'
are no longer necessary and can be removed.
The TCA control option
$GLOBALS['TCA'][$table]['ctrl']['is_static']
has been removed, as it is
no longer evaluated by the TYPO3 Core.
Originally, this option was introduced to mark certain database tables (for
example, from
static_info_tables
) as containing static, non-editable
reference data. Over time, the TYPO3 ecosystem has evolved, and the original
purpose of is_static has become obsolete.
Modern TYPO3 installations rarely rely on static data tables. Better mechanisms
now exist for managing read-only or reference data, such as the TCA options
readOnly and editlock, or backend access control. Removing this
legacy option improves maintainability and reduces complexity for newcomers.
Impact
The option is_static is no longer evaluated. It is automatically removed
at runtime by a TCA migration, and a deprecation log entry is generated to
indicate where adjustments are required.
Affected installations
All TYPO3 installations defining
$GLOBALS['TCA'][$table]['ctrl']['is_static']
in their TCA configuration
are affected.
Migration
Remove the is_static option from the ctrl section of your TCA
configuration.
Breaking: #106869 - Remove static function parameter in AuthenticationService
The method
\TYPO3\CMS\Core\Authentication\AuthenticationService::processLoginData()
no longer accepts the parameter
$passwordTransmissionStrategy
.
Additionally, the method now declares a strict return type.
Impact
Authentication services extending or overriding
AuthenticationService
and its method
processLoginData()
(or a subtype such as
processLoginDataBE()
or
processLoginDataFE()
) will no longer
receive the
$passwordTransmissionStrategy
parameter.
Affected installations
TYPO3 installations with custom authentication services that extend
AuthenticationService
and implement or override
processLoginData()
or one of its subtypes.
Migration
Extensions extending
AuthenticationService
must remove the
$passwordTransmissionStrategy
parameter from their
method signature and add the strict return type
bool|int
.
Extensions implementing subtype methods such as
processLoginDataBE()
or
processLoginDataFE()
must also remove the
parameter, as it is no longer passed to these methods.
The TSconfig option
mod.web_list.noViewWithDokTypes
has been
removed, as it duplicated the existing configuration
TCEMAIN.preview.disableButtonForDokType
.
Since forge#96861, the latter has been established as the single source of
truth for disabling the View button for specific
doktype
values.
Impact
The option
mod.web_list.noViewWithDokTypes
is no longer evaluated.
Only the configuration
TCEMAIN.preview.disableButtonForDokType
is now respected.
Affected installations
TYPO3 installations that still rely on
mod.web_list.noViewWithDokTypes
in Page TSconfig to control the
visibility of the View button in backend modules are affected.
Migration
Remove any usage of
mod.web_list.noViewWithDokTypes
from Page
TSconfig.
Use the existing configuration
TCEMAIN.preview.disableButtonForDokType
instead:
With forge#105640, context awareness for the CKEditor Rich Text Editor (RTE)
was introduced, allowing the editor interface to automatically adapt to the
user’s system-wide light or dark mode preference.
This feature is now enabled by default. The TYPO3 Core stylesheet
EXT:rte_ckeditor/Resources/Public/Css/contents.css has been updated to
support light and dark mode variants automatically. Previously fixed white
backgrounds now adapt dynamically based on the editor’s preferred color scheme.
Note that this change affects only the backend editor interface. The display of
RTE content in the frontend remains unaffected.
Impact
The previously fixed light mode user interface of the CKEditor RTE is now
context-aware, displaying content in light or dark mode according to the
editor’s system preference.
Affected installations
TYPO3 installations that rely on a fixed light mode presentation of CKEditor
RTE instances in the backend are affected.
Migration
Installations with custom CKEditor modifications should review their
contents.css file.
If the TYPO3 Core default stylesheet was previously used, and a fixed light
mode appearance is desired, this can be enforced in the RTE YAML
configuration:
The TCA control option
$GLOBALS['TCA'][$table]['ctrl']['searchFields']
has been removed.
With the introduction of the Schema API and the
\TYPO3\CMS\Core\Schema\SearchableSchemaFieldsCollector
component,
the handling of fields included in searches has changed. By default, all
fields of suitable types, such as input or text, are now automatically
considered searchable.
To manually define searchable fields, use the new
searchable
field
configuration option within a field's TCA configuration.
See the full list of supported field types
here.
Unsupported field types (such as file, inline, etc.) are not considered
searchable and do not support the searchable option.
Note
This change does not affect the Page TSconfig option used by
EXT:linkvalidator:
mod.linkvalidator.searchFields
.
Impact
The searchFields option is no longer evaluated. It is automatically
removed at runtime through a TCA migration, and a deprecation log entry is
generated to highlight where adjustments are required.
If suitable fields are detected that were
not listed in the removed
searchFields
option, they are automatically
set to
searchable => false
to preserve previous behavior.
Affected installations
All installations that define searchFields in their TCA configuration.
Migration
Remove the searchFields option from the ctrl section of your TCA
configuration.
If needed, use the searchable option in individual field definitions
to control which fields are included in the search functionality.
Breaking: #106976 - Removal of TCA search field configuration options
The following TCA field-level search configuration options have been removed:
search.case
search.pidonly
search.andWhere
These options were originally intended to customize backend record search
behavior but have proven to be of little practical value:
They were not used in the TYPO3 Core,
They were not used in common third-party extensions,
Their behavior was unclear and inconsistently supported,
They were insufficiently documented and hard to validate.
This removal is part of the ongoing effort to simplify and streamline TCA
configuration and reduce unnecessary complexity for integrators.
Impact
These options are no longer evaluated. They are automatically removed at
runtime through a TCA migration, and a deprecation log entry is generated to
highlight where adjustments are required.
Affected installations
Any installation or extension that defines one or more of these options in its
TCA field configuration:
One of the main features of TCA is the concept of record types. This allows
using a single table for different purposes and in different contexts. The
most well-known examples are the "Page Types" of the
pages
table and the
"Content Types" of the
tt_content
table. For every specific type, it is
possible to define which fields to display and to customize their
configuration.
A special case historically has been plugin registration, which for a long
time used the so-called subtypes feature of TCA. This was an additional layer
below record types, configured using subtype_value_field (commonly
list_type), and optionally subtypes_addlist and
subtypes_excludelist to add or remove fields depending on the selected
subtype.
FlexForms attached to such subtypes were configured using
ds_pointerField (typically pointing to list_type,CType). This came in
combination with the corresponding ds configuration, which was an array
with keys combining the pointer fields, for example:
Over recent TYPO3 versions, this approach has been deprecated in favor of
using record types exclusively for plugin registration via the CType field,
making configuration cleaner and easier to understand.
With this change, support for ds_pointerField and the multi-entry
ds array format has now been removed. The ds option now points to
a single FlexForm, either directly or via a FILE: reference.
FlexForms must instead be assigned via standard types configuration
using columnsOverrides.
This also affects the data structure identifier, which in the commonly used
tca type is the dataStructureKey. It is now set to default if the
table does not support record types or no record type-specific configuration
exists. Otherwise, the dataStructureKey is set to the corresponding
record type value, for example textpic.
A fallback for TYPO3 v14 resolves comma-separated dataStructureKey
values (for example, list_type,CType) to CType. Such comma-separated
keys were used to address flex form fields in page TSconfig overrides via
TCEFORM.<table>.<field>.<dataStructureKey> and in backend user exclude-field
definitions via <table>:<field>;<dataStructureKey>. This fallback emits a
PHP deprecation notice and is removed in TYPO3 v15, so page TSconfig and
exclude-field addressing then use the data structure key as-is (the record
type value, for example textpic).
To address circular dependencies during schema building,
FlexFormTools
now supports
both TCA Schema objects and raw TCA configuration arrays as input. The
following methods accept a union type
array|TcaSchema
for the new
$schema
parameter:
getDataStructureIdentifier()
parseDataStructureByIdentifier()
cleanFlexFormXML()
Previously, these methods relied on
$GLOBALS['TCA']
internally, which
caused architectural issues. They now operate directly on the provided schema.
All calls to these methods should provide the
$schema
parameter with
either a
TcaSchema
instance or a raw TCA
configuration array. Since data structure resolution can be customized by
extensions, the parameter is not strictly mandatory, but it is strongly
recommended to provide it in most cases. An
InvalidTcaSchemaException
will be thrown if schema resolution is required but no schema is passed.
This change also enables components like
RelationMapBuilder
to use
FlexFormTools
during schema
building, even when only raw TCA is available.
The following class has been removed as it is no longer required:
InvalidCombinedPointerFieldException
Impact
FlexForm Pointer Field Removal
Any TCA definition that still uses
ds_pointerField
or a ds
array with multiple entries (for example news_pi1,list) will no longer work
and might cause rendering errors.
FlexFormTools Schema Parameter
All code calling
FlexFormTools
methods (
getDataStructureIdentifier()
,
parseDataStructureByIdentifier()
,
cleanFlexFormXML()
) must be
updated to provide the required
$schema
parameter.
Affected installations
FlexForm Pointer Field Removal
All installations using
ds_pointerField
(as the pointer field
functionality has been removed entirely) or an array-like structure for
ds in their TCA field type flex configuration.
FlexFormTools Schema Parameter
All installations with custom code that directly call
FlexFormTools
methods
without providing the
$schema
parameter.
This includes custom extensions or TYPO3 Core patches using these methods.
A TCA migration automatically converts single-entry ds arrays.
Multi-entry definitions require manual migration, as they must be aligned with
the correct record type configuration, which may require additional
configuration changes beforehand.
Example for single-entry migration:
Before:
Migration of single-entry ds configuration (before)
'ds' => [
'default' => '<T3DataStructure>...',
],
Copied!
After:
Migration of single-entry ds configuration (after)
Extbase no longer supports PHP annotations for models, data transfer objects
(DTOs), and controller actions. Use PHP attributes (for example
#[Extbase\Validate]
) instead. Attributes provide the same functionality
with better performance and native language support.
PHPDoc annotations such as
@var
,
@return
, and similar are
unaffected by this change, as they are handled by standard PHPDoc parsing,
not by Doctrine.
Explicit type annotations like
@var ObjectStorage<FileReference>
remain fully supported.
Extbase previously relied on annotation parsing, typically detected when the
annotation namespace was imported, for example:
Since TYPO3 now requires PHP 8.2 as the minimum version, the use of native PHP
attributes is preferred. All Extbase-related annotations have been available
as PHP attributes since TYPO3 v12.
Impact
By dropping support for Extbase annotations, only PHP attributes are now
supported. This provides more type safety and better integration with PHP’s
language features.
Developers benefit from a cleaner, faster, and more reliable implementation
without the need for the deprecated third-party annotation parser.
Affected installations
All Extbase models, Data Transfer Objects (DTOs), and controllers that use
Extbase annotations are affected.
This includes built-in annotations such as
Cascade
,
Lazy
, and
Validate
, as well as any custom annotation implementations.
Migration
Switch from Extbase annotations to native PHP attributes.
Attribute class namespaces have moved from
\TYPO3\CMS\Extbase\Annotation
to
\TYPO3\CMS\Extbase\Attribute
.
A class alias map remains available for backward compatibility, but since
the old namespaces are deprecated, developers should migrate to the new
Attribute
namespace to prevent issues in the future.
Additionally, backward compatibility for the abstract base class
constructor has been removed:
\TYPO3\CMS\Core\Cache\Backend\AbstractBackend
Impact
If you are using or extending the Cache Backend or Cache Frontend, you must
ensure that you are using the correct PHP types. This includes method
parameters, return types, and property types in your classes.
Affected installations
All installations that extend or implement cache backend or frontend
classes are affected. This includes custom cache implementations in
third-party extensions.
Extension authors who have created custom cache backends or frontends
will need to update their class method signatures to match the new type
declarations from the interfaces.
Migration
Update your custom cache backend and frontend implementations to use the
correct PHP types:
Ensure all method signatures match the interface declarations exactly
Add proper type hints for parameters (e.g.,
string
$entryIdentifier
)
Note for extensions that strive for TYPO3 v13 and v14 compatibility: The
__construct()
change of
\TYPO3\CMS\Core\Cache\Backend\AbstractBackend
can be mitigated by
omitting a type for the first argument and checking whether its incoming
value is a string (v13) or an array (v14).
For interface changes that added types to method argument signatures,
implementing services may omit the type to keep backwards compatibility
with TYPO3 v13. For added return value types, they must be added for v14
compatibility, and v13 should accept them. All in all, it should be
possible to have a single implementing class for both v13 and v14, but
it may be somewhat tricky. From a code perspective, it may be easier to
have dedicated classes.
The workspace record flag Freeze Editing has been removed without
replacement, effectively removing this feature.
The functionality had only been partially implemented and exhibited several
usability and conceptual issues. It was therefore decided to remove it entirely
in favor of more reliable and flexible workspace configuration options.
Impact
"Freezing" a workspace to prevent editing of records is no longer possible.
Affected installations
Since Freeze Editing did not provide any visible feedback to editors, it was
likely used only rarely.
During the database compare, the field freeze will be removed from the
table
sys_workspace
, effectively "unfreezing" all previously frozen
workspaces.
Migration
A more robust alternative to Freeze Editing is to configure a custom
workspace stage and assign responsible persons.
In combination with the following workspace options:
Publish only content in publish stage
Restrict publishing to workspace owners
you can create a workflow in which only designated members or groups are
permitted to move records into the Ready to publish stage and/or perform
publishing actions.
While other members can still edit records, such edits will automatically reset
the workspace stage back to Editing, restarting the review cycle for those
changes.
The handling of PSR-7 response headers in TYPO3 Core and Extbase has been
unified. Previously, different mechanisms caused inconsistent behavior:
Extbase only kept the last value of a header, discarding all
previous values (e.g. only one Set-Cookie header was possible).
Core allowed multiple Set-Cookie headers, but merged all other
headers with multiple values into a single comma-separated string.
According to RFC 9110, this is only valid for headers that explicitly
support comma-separated lists.
With this change, TYPO3 now preserves multiple header values by default.
Each value is emitted as a separate header line, while single values remain
a single-line header.
Impact
Multiple header values are now always emitted as multiple header lines.
Extbase and Core responses can now properly emit multiple headers with the
same name (e.g. Set-Cookie, WWW-Authenticate, Link,
xkey).
Extensions that relied on the old merging or overwriting behavior may need
to be adapted.
Affected installations
Installations are affected if they:
Relied on headers being merged into a comma-separated string.
Relied on only the last header value being retained in Extbase responses.
Migration
If your use case requires merged header values, you must now implement this
explicitly:
Note: There is another edge case not affected by this change. Multiple Extbase
plugins still cannot set multiple header values with the same name (for
example, two Extbase plugins both setting a Set-Cookie header). In this
case, the latter will override the former. Installations affected by this
scenario should resolve it by adding their own middleware.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate']
has been removed in favor of the PSR-14 event
BeforeFormIsCreatedEvent
, which provides a
more powerful and flexible way to influence form creation.
Impact
Any hook implementation registered under
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate']
is no longer executed in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The Extension Scanner will report such usages as a weak match.
Migration
The hook has been removed without prior deprecation.
This allows extensions to remain compatible with both TYPO3 v13 (using the
hook) and v14+ (using the new event) simultaneously.
The Content > List backend module has been refactored to use the
RecordInterface
and related Record API
internally instead of working with raw database row arrays.
This modernization introduces stricter typing and improves data consistency.
As a result, several public method signatures have been updated.
The following public methods in
DatabaseRecordList
have changed
their signatures:
renderListRow()
now expects a
RecordInterface
object instead
of an array as the second parameter.
makeControl()
now expects a
RecordInterface
object instead of
an array as the second parameter.
makeCheckbox()
now expects a
RecordInterface
object instead of
an array as the second parameter.
languageFlag()
now expects a
RecordInterface
object instead of
an array as the second parameter.
makeLocalizationPanel()
now expects a
RecordInterface
object
instead of an array as the second parameter.
linkWrapItems()
now expects a
RecordInterface
object instead
of an array as the fourth parameter.
getPreviewUriBuilder()
now expects a
RecordInterface
object
instead of an array as the second parameter.
isRecordDeletePlaceholder()
now expects a
RecordInterface
object instead of an array.
isRowListingConditionFulfilled()
has dropped the first parameter
$table
and now expects a
RecordInterface
object instead of an
array.
These changes enable the List module to operate on structured Record objects,
providing better type safety, consistency, and a foundation for further
modernization of the backend record handling.
Impact
Code that calls these methods directly must be updated to pass
RecordInterface
objects instead of
database row arrays.
Affected installations
TYPO3 installations with custom extensions that:
Extend or XCLASS
DatabaseRecordList
and override
any of the affected methods.
Call the affected methods directly with array-based record data.
Migration
When calling affected methods, use the Record API to create a Record object
from a database row:
Before:
Migrating from array-based record handling to the Record API (before)
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate']
has been removed in favor of the more powerful PSR-14 event
BeforeFormIsDuplicatedEvent
.
Impact
Implementations of the removed hook are no longer executed in TYPO3 v14.0
and later.
Affected installations
TYPO3 installations with custom extensions using this hook. The extension
scanner reports any usage as a weak match.
Migration
The hook has been removed without deprecation to allow extensions to remain
compatible with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new
event). When implementing the event as well, no further deprecations will
occur.
Use the PSR-14 Event to achieve the same
or greater functionality.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete']
has been removed in favor of the more powerful PSR-14 event
\TYPO3\CMS\Form\Event\BeforeFormIsDeletedEvent
.
Impact
Implementations of the removed hook are no longer executed in TYPO3 v14.0
and later.
Affected installations
TYPO3 installations with custom extensions using this hook. The extension
scanner reports any usage as a weak match.
Migration
The hook has been removed without deprecation to allow extensions to remain
compatible with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new
event). When implementing the event as well, no further deprecations will
occur.
Use the PSR-14 Event to achieve the same
or greater functionality.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave']
has been removed in favor of the more powerful PSR-14 event
\TYPO3\CMS\Form\Event\BeforeFormIsSavedEvent
.
Impact
Implementations of the removed hook are no longer executed in TYPO3 v14.0
and later.
Affected installations
TYPO3 installations with custom extensions using this hook. The extension
scanner reports any usage as a weak match.
Migration
The hook has been removed without deprecation to allow extensions to remain
compatible with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new
event). When implementing the event as well, no further deprecations will
occur.
Use the PSR-14 Event to achieve the same
or greater functionality.
Breaking: #107397 - Circular dependency between ProcessedFile and Task removed
The circular dependency between
\TYPO3\CMS\Core\Resource\ProcessedFile
and File Processing Task
classes has been resolved to improve the architecture and maintainability of
the File Abstraction Layer (FAL) processing system.
The following changes have been made to
ProcessedFile
:
The public method
getTask()
has been removed.
The public method
generateProcessedFileNameWithoutExtension()
has been
removed.
The following changes have been made to
ProcessedFileRepository
:
Method
add()
now requires a
TaskInterface
parameter.
Method
update()
now requires a
TaskInterface
parameter.
Additionally, the checksum validation logic has been moved from
ProcessedFile
to
AbstractTask
.
Impact
Any code that calls the following methods will cause PHP fatal errors:
Any code that calls
ProcessedFileRepository->add()
or
ProcessedFileRepository->update()
without the new
TaskInterface
parameter will cause PHP fatal errors.
Code that relied on TYPO3CMSCoreResourceProcessedFile objects
having Task objects available internally will no longer work, as Task objects
are now created externally by
FileProcessingService
when
needed.
Affected installations
Installations with custom file processing extensions or custom Task
implementations that directly interact with the
ProcessedFile->getTask()
method are affected. The extension scanner will
report any usage of
ProcessedFile->getTask()
and
ProcessedFile->generateProcessedFileNameWithoutExtension()
as weak
matches.
Extensions that manually call
ProcessedFileRepository->add()
or
ProcessedFileRepository->update()
are also affected. The extension
scanner will not report usages of these methods due to too many weak matches.
Migration
Replace calls to
ProcessedFile->getTask()
with direct creation of Task
objects through the
TaskTypeRegistry
:
The helper classes for preview and CropScaleMask (CSM) image generation
prevented further unification of the File Abstraction Layer (FAL) image
processing API.
The two helper classes
\TYPO3\CMS\Core\Resource\Processing\LocalPreviewHelper
and
\TYPO3\CMS\Core\Resource\Processing\LocalCropScaleMaskHelper
have been removed. Their functionality has been merged into
\TYPO3\CMS\Core\Resource\Processing\LocalImageProcessor
.
These helper classes existed for historical reasons and were never intended
to be part of the public API.
Impact
Any code that extends or references
LocalPreviewHelper
or
LocalCropScaleMaskHelper
will now trigger PHP fatal errors.
Affected installations
Installations with custom extensions that extend or reference either
LocalPreviewHelper
or
LocalCropScaleMaskHelper
are affected and will cause PHP fatal errors.
These helper classes were meant to be internal but were never declared as
such. Implementations using them directly instead of the
LocalImageProcessor
should
be very rare.
Migration
Remove all references to
LocalPreviewHelper
or
LocalCropScaleMaskHelper
from your code.
Use the
LocalImageProcessor
directly instead, or implement a custom image processor that executes before
this processor to apply additional functionality.
Breaking: #107436 - Localization system architecture changes
The TYPO3 localization system has been migrated to use the Symfony Translation
components internally (see Feature: #107436 - Symfony Translation Component integration), which introduces
several breaking changes to the internal API and configuration handling.
This change affects only the internal processing of translation ("locallang")
files. The public API of the localization system remains unchanged.
Method Signature Changes
The method
\TYPO3\CMS\Core\Localization\LocalizationFactory::getParsedData()
has a modified signature and behavior:
It now only returns the parsed and combined localization data instead of both
the default and the localized data. To obtain the default (English) data, call
getParsedData($fileReference, 'en')
.
Language Key Changes
Internally, the fallback language key has been changed from default to en.
This does not affect public API usage, where default can still represent any
configured language key other than English.
Configuration Changes
Custom parser configuration is no longer supported.
The global configuration option
$GLOBALS['TYPO3_CONF_VARS']['SYS']['lang']['parser']
has been removed,
and any custom parser registered through it will be ignored.
Several configuration options have been moved or renamed:
Code calling
\TYPO3\CMS\Core\Localization\LocalizationFactory::getParsedData()
with the old signature or expecting a multi-dimensional array structure will
break:
The method now enforces strict parameter types.
The method returns en instead of default for English translations.
Unused parameters have been removed.
Extensions that register custom localization parsers using
$GLOBALS['TYPO3_CONF_VARS']['SYS']['lang']['parser']
will no longer have
their parsers executed, potentially leading to missing translations.
Instances using the moved configuration options must update their configuration
to the new paths. The old options are no longer recognized and will be ignored.
Since TYPO3 v13, the basic
lib.parseFunc
and
lib.parseFunc_RTE
configurations are always available through
EXT:frontend/ext_localconf.php.
The default parseFunc configuration previously provided by
EXT:fluid_styled_content has been removed to avoid duplicated and outdated
settings. This unifies the parseFunc behavior between EXT:frontend and
EXT:fluid_styled_content.
The
allowTags
syntax is no longer set by default, as HTML
sanitization has been handled properly by the HTML Sanitizer for some time.
All HTML tags are now allowed by default in frontend output, while the
HTML Sanitizer controls which tags and attributes are ultimately permitted.
Note that the
allowTags
directive itself has not been
removed. It can still be set to restrict frontend output where desired.
The conceptual approach is:
The backend (RTE and its HTML parser/processing) already cleans unwanted
content and controls what is stored in the database.
The frontend output (parseFunc) should only add additional restrictions
through
allowTags
when content comes from external or
untrusted sources (for example, custom Extbase output).
Impact
Custom TypoScript configurations using
allowTags
syntax may no
longer work as expected. Specifically:
allowTags := addToList(wbr)
no longer appends wbr.
Instead, it limits allowed tags to only wbr.
Default CSS classes on HTML elements (for example
<table class="contenttable">
) are no longer automatically added by
the parseFunc configuration.
The parseFunc configuration in EXT:fluid_styled_content no longer
provides custom link-handling options such as extTarget and keep.
Affected installations
TYPO3 installations that use:
Custom TypoScript configurations relying on prior default
allowTags
behavior.
Extensions or sites depending on the specific parseFunc configuration
previously provided by EXT:fluid_styled_content.
Configurations expecting automatic CSS class additions to HTML elements.
Sites relying on the old external link-handling behavior from the
Fluid Styled Content parseFunc.
Migration
If you need to allow specific HTML tags, explicitly configure
allowTags
instead of extending a former default.
Before (no longer works):
lib.parseFunc_RTE.allowTags := addToList(wbr)
Copied!
After:
lib.parseFunc_RTE.allowTags = b,span,i,em,wbr,...
Copied!
For custom CSS classes on HTML elements, use custom CSS or add them through
Fluid templates or TypoScript processing instead.
If you require the previous link-handling behavior, configure it explicitly:
The TYPO3 Modal component has been migrated from Bootstrap's modal
implementation to use the native HTML
<dialog>
element. This improves
accessibility, reduces bundle size, and removes the dependency on Bootstrap's
JavaScript for modal functionality.
As part of this migration, Bootstrap's native modal events (such as
show.bs.modal
,
shown.bs.modal
,
hide.bs.modal
, and
hidden.bs.modal
) are no longer dispatched.
Likewise, direct Bootstrap modal API usage (for example
new Modal()
from
Bootstrap or
$(element).modal()
) is no longer supported.
Impact
Bootstrap modal events (
*.bs.modal
) are no longer available.
Extensions listening to these events must migrate to TYPO3's custom modal
events.
The Modal component now uses the native
<dialog>
element with updated
CSS classes and DOM structure.
Any direct manipulation of Bootstrap modal APIs will no longer work.
Extensions using
data-bs-toggle="modal"
,
data-bs-content="..."
, or
data-bs-target
attributes to trigger modals must migrate to TYPO3's
Modal API.
Use Bootstrap's modal JavaScript API directly
(for example
new bootstrap.Modal()
)
Use jQuery to control modals (for example
$(element).modal('show')
)
Use
data-bs-toggle="modal"
or
data-bs-content="..."
attributes
Manipulate modal DOM structures or classes expecting Bootstrap markup
Migration
Event migration
Replace Bootstrap modal event listeners with TYPO3's custom modal events.
Event listeners must be attached to the modal instance returned by the Modal
API, not queried from the DOM.
Before:
const modalElement = document.querySelector('.modal');
modalElement.addEventListener('show.bs.modal', (event) => {
console.log('Modal is about to be shown');
});
modalElement.addEventListener('shown.bs.modal', (event) => {
console.log('Modal is now visible');
});
modalElement.addEventListener('hide.bs.modal', (event) => {
console.log('Modal is about to be hidden');
});
modalElement.addEventListener('hidden.bs.modal', (event) => {
console.log('Modal is now hidden');
});
Copied!
After:
import Modal from'@typo3/backend/modal';
import Severity from'@typo3/backend/severity';
const modal = Modal.show(
'My Modal Title',
'This is the modal content',
Severity.info
);
modal.addEventListener('typo3-modal-show', (event) => {
console.log('Modal is about to be shown');
});
modal.addEventListener('typo3-modal-shown', (event) => {
console.log('Modal is now visible');
});
modal.addEventListener('typo3-modal-hide', (event) => {
console.log('Modal is about to be hidden');
});
modal.addEventListener('typo3-modal-hidden', (event) => {
console.log('Modal is now hidden');
});
Copied!
Bootstrap API migration
Replace Bootstrap modal API calls with TYPO3's Modal API.
Do not instantiate Bootstrap modals or manipulate modal DOM elements directly.
import Modal from'@typo3/backend/modal';
import Severity from'@typo3/backend/severity';
// Show a simple modal
Modal.show(
'My Modal Title',
'This is the modal content',
Severity.info,
[
{
text: 'Close',
btnClass: 'btn-default',
trigger: (event, modal) => modal.hideModal()
}
]
);
// Dismiss the current modal
Modal.dismiss();
Copied!
Data attribute migration
Replace Bootstrap's
data-bs-toggle
and
data-bs-target
attributes
with TYPO3's modal trigger API.
Before:
<buttontype="button"data-bs-toggle="modal"data-bs-target="#myModal"data-bs-content="Are you sure?">
Open Modal
</button>
Copied!
After:
<buttontype="button"class="t3js-modal-trigger"data-title="Confirmation"data-content="Are you sure?"data-severity="warning"data-button-close-text="Cancel"data-button-ok-text="Confirm">
Open Modal
</button>
The TypoScript condition function
getTSFE()
has been removed.
After various properties like
getTSFE().type
were already removed
in TYPO3 v13, the remaining parts of this functionality have now been removed
in TYPO3 v14.
The most common remaining use was accessing the current page ID via
getTSFE().id
, which can be replaced with
request.getPageArguments().getPageId()
.
Impact
Conditions using
getTSFE()
will no longer evaluate to true and
must be updated.
Affected installations
Instances with TypoScript conditions that use the function
getTSFE()
are affected.
Migration
Replace
getTSFE()
with an equivalent condition. For example:
The following method in
\TYPO3\CMS\Core\Core\Environment
has been
removed in TYPO3 v14.0:
Environment::getComposerRootPath()
Since composer installers v4/v5 (which are required since TYPO3 v12),
getComposerRootPath()
and
getProjectPath()
return the same
value, because the project path can no longer be changed through
configuration.
Therefore, the method
Environment::getComposerRootPath()
has been
removed. It was marked as internal from the beginning.
Impact
Calling this method will result in a PHP error.
Affected installations
TYPO3 installations with custom extensions or custom code that directly
call the removed method are affected:
Environment::getComposerRootPath()
The extension scanner will report any usage as a strong match.
Migration
Instead of calculating relative paths manually, use absolute paths or
the appropriate TYPO3 APIs for path handling.
Use the following replacement:
Environment::getProjectPath()
Breaking: #107488 - Scheduler frequency options moved to TCA
The global configuration array
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['frequencyOptions']
,
which was used to define frequency options for scheduler tasks, has been
removed.
Frequency options are now configured directly in the TCA using the
overrideFieldTca
mechanism on the
tx_scheduler_task.execution_details
field.
This change improves consistency with TYPO3’s configuration patterns and
provides better extensibility for scheduler task timing options.
Impact
Extensions that previously added custom frequency options through the global
frequencyOptions
array will no longer see their custom options in the
scheduler task frequency field.
Code that relied on reading the global
frequencyOptions
configuration
will no longer work as expected.
Affected installations
All installations with extensions providing custom scheduler frequency options
through the global configuration array are affected.
Migration
Extensions should migrate their frequency options from the global configuration
to TCA overrides.
The method
\TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher->getTypoScriptFrontendController()
has been removed.
Since the entire
TypoScriptFrontendController
class is being phased out,
this abstract helper method has been removed as part of that cleanup.
Impact
Calling this method in a custom EXT:form finisher will result in a fatal PHP
error.
Affected installations
TYPO3 instances using EXT:form with custom finishers that call this method are
affected. The extension scanner is configured to detect such usages.
Migration
Migration depends on what the finisher previously did with the returned class
instance. The
TypoScriptFrontendController
properties and helper methods
have been modernized, with most data now available as request attributes.
For example, accessing the
cObj
property can be replaced like this:
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\BeforeRenderableIsAddedToFormEvent
.
Impact
Hook implementations registered under
initializeFormElement are no longer executed in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The extension scanner reports any usage as a weak match.
Migration
The hook was removed without a deprecation phase to allow extensions to work
with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new event)
simultaneously.
Use the PSR-14 event instead to influence
form element initialization. Since the event is dispatched at a later point,
it allows more extensive modifications than the previous hook.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\BeforeRenderableIsRemovedFromFormEvent
.
Impact
Hook implementations registered under
beforeRemoveFromParentRenderable
are no longer executed in
TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The extension scanner reports any usage as a weak match.
Migration
The hook was removed without a deprecation phase to allow extensions to
work with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new
event) simultaneously.
Use the PSR-14 event instead to allow
greater influence over the form processing workflow. Since the event is
dispatched at a later point, it allows more extensive modifications than
the previous hook.
Breaking: #107537 - Changes in URL generation of system resources
The following changes are considered breaking, although their impact is
expected to be very low.
TypoScript getData function
path
previously returned a
relative URL and now returns an absolute URL (prepended with
absRefPrefix
).
Access to FAL storages via relative path (fileadmin/templates/main.css)
is limited to the default storage defined in
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']
.
All generated system resource URLs now include cache busting.
Adding custom query strings to resource identifiers no longer disables cache
busting — both are now applied.
page.20 = TEXT
page.20 {
data = path : EXT:core/Resources/Public/Icons/Extension.svg
}
Copied!
Result (TYPO3 classic mode – note the leading "/")
"path" result before: typo3/sysext/core/Resources/Public/Icons/Extension.svg
"path" result now: /typo3/sysext/core/Resources/Public/Icons/Extension.svg
Copied!
Result (TYPO3 Composer mode – note the leading "/")
"path" result before: _assets/5f237792cbcdc97cfceade1e16ea33d7/Icons/Extension.svg
"path" result now: /_assets/5f237792cbcdc97cfceade1e16ea33d7/Icons/Extension.svg
Copied!
Relative path to FAL Storage
Referencing resources via relative path does only work for the default FAL storage
defined in
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']
.
This means if you have other local FAL storages configured, you need to use the FAL resource syntax
(e.g. FAL:42:/path/to/file.css) to reference files in such storage instead of using relative paths
(e.g. my-other-fileadmin/path/to/file.css).
It is generally recommended to use explicit resource identifiers (App resources or FAL resources),
instead of relative paths.
All generated URLs now contain cache busting
All generated resource URLs now include cache busting.
For example, icon URLs that previously had no cache busting will now contain a
cache-busting query string.
Additional query strings applied to the resource identifier
When adding custom query strings to resource identifiers, TYPO3 previously
disabled cache busting.
Now, both the custom query string and the cache-busting parameter are applied.
If custom query strings were used as manual cache busters, you can now remove
them safely.
page.20 = TEXT
page.20 {
data = asset : EXT:core/Resources/Public/Icons/Extension.svg?v=1234
}
Copied!
Result
result before: /typo3/sysext/core/Resources/Public/Icons/Extension.svg?v=1234
result now: /typo3/sysext/core/Resources/Public/Icons/Extension.svg?v=1234&1709051481
Copied!
Impact
getText "path" in TypoScript
All usages of
path
in TypoScript
data
now resolve
to absolute URLs instead of relative ones.
Relative path to FAL Storage
In installations referencing resources in additional local FAL storages using
a relative path syntax, an exception is thrown.
All generated URLs now contain cache busting
Generated URLs differ slightly from previous TYPO3 versions, especially when
cache busting was not applied before.
Additional query strings applied to the resource identifier
URLs now include both the original query string and the cache-busting
parameter, resulting in different output compared to earlier TYPO3 versions.
Affected installations
getText "path" in TypoScript
All installations using
path
in TypoScript
data
.
Relative path to FAL Storage
All installations referencing resources in additional local FAL storages using
a relative path syntax (e.g. my-other-fileadmin/path/to/file.css).
All generated URLs now contain cache busting
All installations having third party code, that misuses generated URLs
to assume file system paths from them.
Additional query strings applied to the resource identifier
All installations using resource identifiers with custom query strings, for
example:
EXT:foo/Resources/Public/rte.css?v=123
Migration
Relative path to FAL Storage
Either convert the relative path to a FAL resource like so:
FAL:1:/path/to/file.css
Alternatively it is possible to convert it to a resource URI:
URI:/my-other-fileadmin/path/to/file.css
The first will add cache busting, the latter will use the URI as is.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterInitializeCurrentPage']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\AfterCurrentPageIsResolvedEvent
.
Impact
Hook implementations registered under afterInitializeCurrentPage are
no longer executed in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The extension scanner reports any usage as a weak match.
Migration
The hook was removed without a deprecation phase to allow extensions to work
with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new event)
simultaneously.
Use the PSR-14 event instead to allow
greater influence over the form rendering process. Since the event is
dispatched at a later point, it allows more extensive modifications than the
previous hook.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterSubmit']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\BeforeRenderableIsValidatedEvent
.
Impact
Hook implementations registered under
afterSubmit
are no longer executed
in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The extension scanner reports any usage as a weak match.
Migration
The hook was removed without a deprecation phase to allow extensions to work
with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new event)
simultaneously.
Use the PSR-14 event instead to allow
greater influence over the form submission process. Since the event is
dispatched at a later point, it allows more extensive modifications than the
previous hook.
The hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRendering']
has been removed in favor of the PSR-14 event
\TYPO3\CMS\Form\Event\BeforeRenderableIsRenderedEvent
.
Impact
Hook implementations registered under
beforeRendering
are no longer
executed in TYPO3 v14.0 and later.
Affected installations
TYPO3 installations with custom extensions using this hook are affected.
The extension scanner reports any usage as a weak match.
Migration
The hook was removed without a deprecation phase to allow extensions to work
with both TYPO3 v13 (using the hook) and TYPO3 v14+ (using the new event)
simultaneously.
Use the PSR-14 event instead to allow
greater influence over the rendering process. Since the event is dispatched
at a later point, it allows more extensive modifications than the previous
hook.
The frontend rendering event
\TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent
has been adjusted due to the removal of the
TypoScriptFrontendController
class.
The method
getController()
has been removed and replaced by two new
methods:
getContent()
and
setContent()
.
Impact
Event listeners that call
getController()
will trigger a fatal PHP
error and must be adapted.
Affected installations
This event is commonly used in frontend rendering–related extensions, since
it provides an opportunity to access and manipulate the fully rendered
response body content at a late point in the rendering chain.
Instances with extensions listening to
AfterCacheableContentIsGeneratedEvent
may be
affected. The extension scanner will detect such usages.
Migration
In most cases,
AfterCacheableContentIsGeneratedEvent->getController()
was
used within event listeners to get and modify the
TypoScriptFrontendController->content
property at the end of the
rendering process.
The event now provides
getContent()
and
setContent()
methods
to achieve the same goal more directly.
The
typo3/cms-adminpanel
system extension provides the interface
\TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface
. It can be used by
extensions that extend the Admin Panel with custom modules and allows storing
additional request-related data in the Admin Panel–specific data store.
The signature of the interface method
getDataToStore()
has changed.
Impact
Extension authors may benefit from the additional argument passed with TYPO3
v14, but implementations must be adjusted accordingly.
Affected installations
Most installations are not affected, as few extensions extend the Admin Panel.
Instances with classes implementing
DataProviderInterface
are affected.
The
getDataToStore()
method is called by the Admin Panel after the
Response
has been created by the TYPO3 Core.
Starting with TYPO3 v14, the method receives the
ResponseInterface
as an additional argument:
The random subpage option for shortcut pages has been removed from TYPO3
Core.
This option allowed shortcut pages to redirect to a random subpage, which was
problematic for caching and resulted in unpredictable behavior - a "random"
page was not truly random, as the page linking to this shortcut was cached.
The following changes have been made:
The class constant
\TYPO3\CMS\Core\Domain\Repository\PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE
has been removed.
The method signature of
\TYPO3\CMS\Core\Domain\Repository\PageRepository::resolveShortcutPage()
has changed from
resolveShortcutPage(array $page, bool $resolveRandomSubpages = false, bool $disableGroupAccessCheck = false)
to
resolveShortcutPage(array $page, bool $disableGroupAccessCheck = false)
.
The TCA configuration for
pages.shortcut_mode
no longer includes the
option Random subpage of selected/current page.
Impact
Code using the removed constant
\TYPO3\CMS\Core\Domain\Repository\PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE
will fail with a PHP fatal error.
Code calling
PageRepository::resolveShortcutPage()
with three parameters,
where the second parameter was
$resolveRandomSubpages
, will fail. The
second parameter is now
$disableGroupAccessCheck
.
Shortcut pages configured to use random subpage mode will now behave as if
they were configured for first subpage mode.
Affected installations
TYPO3 installations with:
Extensions using the constant
PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE
Extensions calling
PageRepository::resolveShortcutPage()
with the
$resolveRandomSubpages
parameter
Extensions extending
PageRepository
and overriding
getPageShortcut()
Shortcut pages configured with random subpage mode in the database
Migration
Remove any usage of
PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE
.
Update calls to
PageRepository::resolveShortcutPage()
to remove the
$resolveRandomSubpages
parameter:
For shortcut pages configured with random subpage mode, update the database
records to use a different shortcut mode (for example, first subpage or a
specific target page):
UPDATE pages
SET shortcut_mode = 1WHERE shortcut_mode = 2;
Copied!
Breaking: #107677 - Drop prepend and append modes from TCA value picker
The prepend and append modes of the value picker specified in TCA and used
in FormEngine (for example
$GLOBALS['TCA']['tx_example']['columns']['example']['config']['valuePicker']['mode']
)
have been removed.
These modes were designed to insert predefined values before or after the
existing input value, but they served only niche use cases and caused
inconsistent behavior across different input types.
Maintaining these modes introduced unnecessary complexity in both
implementation and accessibility. Prepending or appending content dynamically
to user input fields could easily lead to unexpected results, break input
validation, and interfere with assistive technologies such as screen readers.
Additionally, this approach mixed presentation and data logic in ways that are
not consistent with modern form handling patterns.
Future improvements to the value picker will focus on a consistent
mode=replace behavior and may be implemented as new form input types to
provide a more robust and accessible user experience.
Impact
Any value picker TCA configuration using the mode options prepend or
append will no longer have any effect. TYPO3 ignores these settings, and the
picker defaults to the standard replace behavior.
Affected installations
Installations using custom field wizard configurations or integrations that
rely on the value picker with
mode = prepend
or
mode = append
are affected.
Migration
There is no direct replacement for the removed modes.
If your implementation depends on prepending or appending content to existing
values, implement a custom input type or custom form element to handle
this behavior explicitly. This allows you to maintain full control over the
user interface, data handling, and accessibility.
For most use cases, it is recommended to replace prepend or append logic with
the standard replace value picker behavior or use dedicated UI controls that
clearly indicate how values are modified.
If the value picker was used to suggest combinable values, consider listing
these elements in the field description instead so that users can copy and
paste them manually into the input field.
Breaking: #107712 - New method hasSubmoduleOverview() in ModuleInterface
The interface
\TYPO3\CMS\Backend\Module\ModuleInterface
has been extended
with a new method
hasSubmoduleOverview()
to support the new card-based
submodule overview feature introduced in
Feature: #107712 - Introduce card-based submodule overview.
Impact
All custom implementations of
\TYPO3\CMS\Backend\Module\ModuleInterface
must now implement the new
method
hasSubmoduleOverview(): bool
.
Existing implementations that do not implement this method will trigger a PHP
fatal error.
Affected installations
TYPO3 installations with custom PHP code that directly implement the
ModuleInterface
are affected.
This is uncommon, as most backend modules use the provided
\TYPO3\CMS\Backend\Module\Module
class or extend from
\TYPO3\CMS\Backend\Module\BaseModule
.
Migration
Add the
hasSubmoduleOverview()
method to your custom implementation of
ModuleInterface
.
The method should typically return the configured value rather than a fixed
boolean:
useTYPO3\CMS\Backend\Module\ModuleInterface;
classMyCustomModuleimplementsModuleInterface{
protectedarray $configuration = [];
publicfunctionhasSubmoduleOverview(): bool{
// Return the configured value, defaulting to falsereturn$this->configuration['showSubmoduleOverview'] ?? false;
}
}
Copied!
This allows the behavior to be controlled through the module's configuration.
Breaking: #107777 - Use strict types in Extbase class Argument
The method
\TYPO3\CMS\Core\Resource\Index\ExtractorRegistry::registerExtractionService()
has been removed in favor of automatic registration via the interface
\TYPO3\CMS\Core\Resource\Index\ExtractorInterface
.
Registration of metadata extractors now happens automatically when a class
implements the required interface
ExtractorInterface
. No further
registration is necessary.
Impact
Any call to
ExtractorRegistry::registerExtractionService()
is now a
no-op and has no effect in TYPO3 v14.0+. Metadata extractors are automatically
registered via the interface.
Affected installations
TYPO3 installations with custom extensions that register metadata extractor
classes via the mentioned method in ext_localconf.php.
The extension scanner will report such usages.
Migration
The method has been removed without deprecation in order to allow extensions
to work with TYPO3 v13 (using the registration method) and v14+ (using automatic
interface-based registration) without additional deprecations.
Remove the manual registration from ext_localconf.php:
Since custom extractors already implement the required interface
ExtractorInterface
, no further
changes are required inside the extractor class itself.
Breaking: #107784 - Remove backend layout data provider registration via $GLOBALS
The possibility to register backend layout data providers via
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider']
has been replaced by autoconfiguration using the service tag
page_layout.data_provider
.
The tag is automatically added when a class implements
\TYPO3\CMS\Backend\View\BackendLayout\DataProviderInterface
. Manual
configuration via Services.yaml remains possible, especially when
autoconfiguration is disabled.
Additionally, the possibility to dynamically add backend layout data providers
to the global
DataProviderCollection
via its
add()
method has been
removed. Developers should register their data providers as service definitions
in the container as described above.
Impact
Using the global array
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider']
to register backend layout data providers has no effect in TYPO3 v14.0 and
later.
Affected installations
All installations that use
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider']
for backend layout data provider registration are affected.
This registration is typically done in an ext_localconf.php file.
The extension scanner will report such usages.
Migration
Migrate existing registrations to the new autoconfiguration-based approach.
TYPO3 Core TCA and user settings (
$GLOBALS['TYPO3_USER_SETTINGS']
)
configurations have been updated to use short form
translation reference formats (e.g., core.form.tabs:*) instead of
the full LLL:EXT: path format in showitem strings.
This change affects all core TCA showitem definitions that previously
used full LLL:EXT: paths for labels. The most prominent updates are
tab labels using the --div-- syntax, though this pattern may be applied
to other TCA elements in the future.
Examples of changed references in tab labels:
// Before (TYPO3 v13)'--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general''--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access''--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language'
Copied!
// After (TYPO3 v14)'--div--;core.form.tabs:general''--div--;core.form.tabs:access''--div--;core.form.tabs:language'
Copied!
Impact
Custom extensions that programmatically manipulate TCA or
$GLOBALS['TYPO3_USER_SETTINGS']
showitem strings
from core tables and expect the full LLL:EXT: path format will break.
This particularly affects code that:
Uses string search/replace operations on showitem strings to find or
modify specific labels (tabs, palettes, or other elements)
Parses showitem strings using regular expressions expecting the
LLL:EXT: pattern
Extracts translation keys from TCA configurations for analysis or
documentation purposes
Builds custom TCA configurations by copying and modifying core
showitem strings
Currently, the following label categories have been migrated to short-form:
Removed the hardcoded palette name in string --palette--;Capabilities;capabilities
in table
sys_file_storage
in favor of a label attached directly to
the palette using the short syntax core.form.palettes:*.
Field label overrides removed in showitem definitions:
Field labels can be overridden in showitem definitions for types or palettes, but
should rather be kept in the field definition itself. The following field label
overrides have been removed from showitem strings in favor of using the field's
own label definition:
Programmatically read and manipulate TCA showitem strings from
$GLOBALS['TCA']
or
$GLOBALS['TYPO3_USER_SETTINGS']
.
Override core TCA or TYPO3_USER_SETTINGS by copying and modifying
existing showitem configurations
Perform string operations on showitem definitions expecting
specific LLL:EXT: path formats
Generate documentation or analysis tools based on TCA label path references
The extension scanner will not detect these usages, as they involve runtime
string manipulation rather than direct PHP API usage.
Note:
Additional TCA elements beyond tab labels may follow this pattern in future
TYPO3 versions, further extending the use of short-form references in
showitem strings.
Migration
TCA Migration
Extension developers should review their Configuration/TCA/Overrides/
files and any PHP code that manipulates TCA showitem strings
programmatically.
Option 1: Support both formats in string operations
Update your code to handle both the old LLL:EXT: path format and the
new short-form references:
// Before - hardcoded search for old formatif (str_contains($showitem, 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general')) {
// Will not work in TYPO3 v14+
}
Copied!
// After - handle new formatif (str_contains($showitem, 'core.form.tabs:general') ||
str_contains($showitem, 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general')) {
// Works in both versions
}
Copied!
Option 2: Use the TCA API instead of string manipulation
Rather than manipulating showitem strings directly, use TYPO3's TCA
manipulation APIs:
// Instead of string manipulationuseTYPO3\CMS\Core\Utility\ExtensionManagementUtility;
// Add fields using the API
ExtensionManagementUtility::addToAllTCAtypes(
'tx_myext_domain_model_foo',
'my_field',
'',
'after:title'
);
Copied!
Recommended action for all extension developers
Scan your extension's Configuration/TCA/Overrides/ directory and any
PHP code that interacts with showitem strings for patterns such as:
str_contains()
,
str_replace()
,
preg_match()
or similar
string functions operating on showitem values
String operations looking for 'LLL:EXT:' patterns in TCA
configurations
Custom parsing of
$GLOBALS['TCA']
showitem strings expecting
specific path formats
This review is especially important since future TYPO3 versions may further
expand the use of short-form references across additional TCA elements.
TYPO3_USER_SETTINGS migrations
Update your code to handle the new short form references:
Before:
EXT:my_extension/ext_tables.php
// Before - hardcoded search for old format
$showitem = $GLOBALS['TYPO3_USER_SETTINGS']['showitem'];
if (str_contains($showitem, 'LLL:EXT:setup/Resources/Private/Language/locallang.xlf:personal_data')) {
// Will not work in TYPO3 v14+
}
Copied!
After:
EXT:my_extension/ext_tables.php
// After - handle new format
$showitem = $GLOBALS['TYPO3_USER_SETTINGS']['showitem'];
if (str_contains($showitem, 'core.form.tabs:personaldata')
|| str_contains($showitem, 'LLL:EXT:setup/Resources/Private/Language/locallang.xlf:personal_data')) {
// Works in both versions
}
The Reports module has been refactored to use native backend submodules instead
of dynamic service-based report registration. Individual reports are now
registered as proper backend submodules in Configuration/Backend/Modules.php.
As a result, the following public interfaces have been removed:
\TYPO3\CMS\Reports\ReportInterface
\TYPO3\CMS\Reports\RequestAwareReportInterface
Impact
Extensions that register custom reports by implementing
\TYPO3\CMS\Reports\ReportInterface
or
\TYPO3\CMS\Reports\RequestAwareReportInterface
will no longer work.
These reports will no longer appear in the backend Reports module.
Affected installations
TYPO3 installations with custom extensions that provide reports by implementing
ReportInterface
or
RequestAwareReportInterface
.
Migration
Custom reports must be migrated to backend submodules.
The controller should implement a standard PSR-7 request handler that returns a
\Psr\Http\Message\ResponseInterface
instance.
Alternatively, you can create a standalone module with
showSubmoduleOverview enabled if you need to group multiple reports
under your own container module.
Breaking: #107823 - Strict typing and API cleanup in backend template components
The backend template components system (buttons, dropdown items, and menus) has
been modernized with strict type hints, consistent return types, and improved
architecture to enhance type safety and developer experience.
Impact
Extensions that implement or extend backend template components must verify
their type declarations and update any usage of changed methods.
New ComponentInterface
A new
\TYPO3\CMS\Backend\Template\Components\ComponentInterface
has been introduced as the parent interface for both
ButtonInterface
and
DropDownItemInterface
.
This unifies the common contract for all renderable backend components.
Both interfaces now extend
ComponentInterface
,
which defines:
isValid(): bool
getType(): string
render(): string
Custom implementations of
ButtonInterface
or
DropDownItemInterface
will
now trigger a
\TypeError
if these return types are missing.
PositionInterface enforced
The
\TYPO3\CMS\Backend\Template\Components\PositionInterface
now enforces
strict type hints:
getPosition(): string
getGroup(): int
This interface allows buttons to define their own fixed position and group,
which automatically override the position and group parameters passed to
ButtonBar::addButton()
.
Icon nullability
Icons are now consistently nullable across all button types. The
AbstractButton::$icon
property and related getter/setter methods now use
?Icon
instead of
Icon
.
This affects classes extending
AbstractButton
, including
LinkButton
,
InputButton
, and
SplitButton
.
Note
While icons are technically optional at the type level, validation methods
may still require icons for buttons to be considered valid.
Method signature changes
Several methods now have stricter parameter types or modified signatures:
MenuItem::isValid()
and
Menu::isValid()
no longer accept
parameters
AbstractDropDownItem::render()
now declares a
string
return
type
Various setter methods now require strict type hints for their parameters
Return type consistency
Abstract classes now use
static
return types for better inheritance
support, while concrete implementations may use
self
or
static
depending on extensibility requirements.
SplitButton API improvement
The
SplitButton::getButton()
method has been replaced with
getItems()
, which returns a type-safe
SplitButtonItems
DTO
instead of an untyped array.
Old (removed):
publicfunctiongetButton(): array // Returnsarraywithmagickeys 'primary' and 'options'
public readonly AbstractButton $primary
- The primary action button
public readonly array $options
- Array of option buttons
This change improves type safety and prevents runtime errors from accessing
non-existent array keys.
Affected installations
All TYPO3 instances with custom backend components, such as buttons, menus, or
dropdown items, that extend or implement the affected interfaces are impacted.
Migration
Extension authors should:
Verify custom button implementations have correct return types on
interface methods.
Check custom classes extending abstract buttons use proper strict types.
Update `isValid()` calls on
MenuItem
and
Menu
objects
(remove the parameter).
Handle nullable icons when working with
getIcon()
methods.
The frontend rendering related event
\TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent
has been
modified due to the removal of the class
\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
.
The method
getController()
has been removed.
Impact
Event listeners that call
getController()
will now trigger a fatal
PHP error and and must be adapted.
Affected installations
Instances with extensions listening for the event
AfterCachedPageIsPersistedEvent
may be
affected.
The extension scanner will detect and report such usages.
Migration
In most cases, data that was previously retrieved from the
\TYPO3\CMS\Frontend\Controller\`TypoScriptFrontendController
instance can now be accessed through the request object, available via
$event->getRequest()
.
All remaining properties have been removed from
\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
, making the
class a readonly internal service used by the TYPO3 Core only.
The class will be fully removed in a later TYPO3 v14 release.
The following instance access patterns have been removed:
All API methods that returned an instance of
TypoScriptFrontendController
-
usually named
getTypoScriptFrontendController()
or similar - have been
removed as well.
Impact
Any remaining direct or indirect usage of
TypoScriptFrontendController
will
now result in a fatal PHP error.
Affected installations
Extensions that still relied on properties or methods of
TypoScriptFrontendController
are
affected.
The class was already marked internal and breaking in TYPO3 v13.
In particular, extensions that used
AbstractContentObject->getTypoScriptFrontendController()
can now access
the relevant data from the PSR-7 request object, for example:
The following public properties of the PHP class TYPO3CMSCoreDataHandlingDataHandler have been removed:
copyWhichTables
neverHideAtCopy
copyTree
Impact
Accessing or setting the properties will throw a PHP warning and have no effect anymore.
Affected installations
Any installation working with the public properties in a third-party extension.
Migration
The configuration values neverHideAtCopy and copyTree are directly read from the
backend user
BE_USER
object. To modify them, use the following values instead:
// Before
DataHandler->neverHideAtCopy
// After
DataHandler->BE_USER->uc['neverHideAtCopy']
// Before
DataHandler->copyTree
// After
DataHandler->BE_USER->uc['copyLevels']
Copied!
To retain database consistency, the list of tables to be copied now only relied on permissions
of the given backend user. If the user has admin access, all tables will be copied if needed.
If not, all tables with access will be copied.
Breaking: #107871 - Remove backend avatar provider registration via
$GLOBALS
Registering backend avatar providers via
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']
has been replaced by autoconfiguration using the
backend.avatar_provider
service tag. This tag is added automatically when the PHP attribute
#[AsAvatarProvider]
is applied. Manual configuration in
Services.yaml is still possible, particularly if autoconfiguration is
disabled.
Impact
Using the
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']
array has no effect in TYPO3 v14.0 and later.
Affected installations
All installations that use
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['avatarProviders']
to register backend avatar providers are affected. This registration is
typically performed in an ext_localconf.php file. The extension
scanner will report any such usages.
Migration
Migrate existing registrations to the new autoconfiguration-based approach.
If you need to support multiple TYPO3 Core versions simultaneously, ensure that
both registration methods are implemented: the legacy
$GLOBALS
based
registration as well as the new tag-based approach.
Breaking: #107884 - Rework actions to use Buttons API with Components
The record list and file list action system (the "button bar" in every
row of the table-like display) has been completely reworked to use
the Buttons API, utilizing proper component objects instead of plain HTML strings.
This modernization improves type safety, provides better extensibility, and
enables more structured manipulation of action buttons through PSR-14 events.
The following components have been affected by this change:
Buttons can now be placed into ActionGroups, which are identified by the
PHP enum
\TYPO3\CMS\Backend\Template\Components\ActionGroup
and
distinguish between a "primary" and a "secondary" group.
In addition,
\TYPO3\CMS\Backend\Template\Components\ComponentGroup
enhances the ability to group multiple Button API Components into a single data
object and manage their state.
Impact
Extensions that listen to the
ModifyRecordListRecordActionsEvent
or
ProcessFileListActionsEvent
to modify
record or file actions need to be updated.
The events no longer work with HTML strings but with
ComponentInterface
objects
(see forge#107823).
Extensions that directly call
DatabaseRecordList::makeControl()
must
update their code, as the
$table
parameter has been removed.
ModifyRecordListRecordActionsEvent
The method
setAction()
now requires a
ComponentInterface
object, and
getAction()
now returns either null or a
ComponentInterface
instance.
The following methods now expect an
ActionGroup
enum value as the
$group
parameter:
hasAction()
getAction()
removeAction()
getActionGroup()
The method
getRecord()
no longer returns a raw array but an instance of
the Record API.
A new method
getRequest()
has been added to access the current PSR-7
request context.
Removed methods:
getActions()
setActions()
getTable()
ProcessFileListActionsEvent
The
\TYPO3\CMS\Filelist\Event\ProcessFileListActionsEvent
has received
identical API changes to
ModifyRecordListRecordActionsEvent
,
allowing manipulation of items in both supported
ActionGroup
contexts
(primary and secondary).
New methods:
setAction()
getAction()
removeAction()
moveActionTo()
getActionGroup()
getRequest()
Buttons can now also be repositioned or inserted at specific before/after
locations within an action group.
Removed methods:
getActionItems()
setActionItems()
Affected installations
TYPO3 installations with custom PHP code that modifies record or file list
actions, or utilizes the mentioned PSR-14 events, are affected.
This event now behaves identically to the file list event:
actions must be created via the Button API and added as
ComponentInterface
instances using
setAction()
.
The
setActions()
and
getActions()
methods are removed and must be
replaced by distinct calls to
setAction()
or use
getActionGroup()
to access existing actions.
The
getRecord()
method now returns a Record API object instead of an
array.
getTable()
can be replaced with
getRecord()->getMainType()
.
The
resource
property
external
in the
PAGE
properties
includeCSS
,
includeCSSLibs
,
includeJS
,
includeJSFooter
,
includeJSFooterlibs
and
includeJSLibs
is now obsolete.
Instead of marking URIs as URIs with an additional option, prefix the URI, that shall be used with URI:,
or simply use absolute URLs starting with http(s)://, where the prefix is not required.
This URI resource definition will work across the system, not only in TypoScript or AssetCollector/ AssetRenderer.
Impact
Using the external property in TypoScript or the external option in AssetCollector
will have no effect any more. If absolute URLs have been used as resources, everything
will work as before. Relative URIs must be prefixed with URI: from now on, otherwise
an exception is thrown.
Additionally, the string after the URI: keyword must be a valid URI, otherwise an exception
is thrown as well. Before this change, invalid URIs (marked as external) would have been rendered
to HTML, without any obvious feedback for developers or integrators. Browsers then ignored
such invalid references.
Affected installations
TYPO3 installations using the external property in TypoScript or the external option in AssetCollector.
Migration
TypoScript before:
page = PAGE
page.includeCSS {
main = https://example.com/styles/main.css
main.external = 1
other = /styles/main.css
other.external = 1
}
Copied!
TypoScript after:
page = PAGE
page.includeCSS {
main = https://example.com/styles/main.css
other = URI:/styles/main.css
}
The TYPO3 frontend and backend applications previously allowed compressing
their HTTP responses using the configuration options
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
and
$GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel']
.
This feature, which was always disabled by default, has now been removed.
TYPO3 will no longer compress its HTTP responses itself.
Response compression should be handled by the web server rather than the
application layer.
Removing this feature avoids potential conflicts when both TYPO3 and the web
server attempt to compress responses and allows modern web servers to use
advanced compression algorithms such as brotli or zStandard when supported by
the client.
Impact
TYPO3 can no longer compress its HTTP responses.
This responsibility is now fully delegated to the web server.
HTTP response compression had to be explicitly enabled before, so most
installations will not notice a change unless they relied on this setting.
Affected installations
Instances that configured
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
or
$GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel']
to non-zero values
are affected.
Administrators should verify that the web server applies HTTP compression by
checking for a response header such as:
Content-Encoding: gzip
when requesting frontend or backend documents with a header like:
Accept-Encoding: gzip, deflate
All commonly used web servers enable this feature by default.
Migration
The configuration toggles for the backend
$GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel']
and the frontend
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
are
obsolete, existing settings in settings.php configuration files are
actively removed when first using the install tool after upgrade to TYPO3 v14.
Breaking: #107944 - Frontend CSS file processing no longer removes comments and whitespaces
When the TYPO3 frontend was configured to compress included CSS assets, it also
attempted to minify CSS by removing comments and certain whitespace characters.
This behavior has now been removed. The previous implementation was brittle,
especially with modern CSS syntax, and provided no measurable performance
benefit in either file transfer or client-side parsing.
Impact
CSS asset files included in frontend pages may become slightly larger if they
contain many comments. TYPO3’s internal CSS parsing was disabled by default and
only active when explicitly enabled using
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
along with additional TypoScript configuration.
In most cases, this change has minimal or no practical impact.
Affected installations
Instances that actively used TYPO3's built-in CSS parsing feature for frontend
asset management are affected.
Migration
If minimizing CSS file size is important, consider one of the following options:
Optimize or minify CSS files manually during deployment.
Accept that comments and whitespace are retained (usually negligible impact).
Preferably, integrate a dedicated frontend build chain to handle CSS and
JavaScript minification.
Modern frontend build tools provide many additional advantages, such as
linting, syntax validation, and advanced optimizations, which are beyond the
scope of the TYPO3 Core.
Breaking: #107945 - Class FlexFormService merged into FlexFormTools
The class
\TYPO3\CMS\Core\Service\FlexFormService
has been merged into
\TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
.
The following methods are affected:
FlexFormService->convertFlexFormContentToArray($flexFormContent, $languagePointer = 'lDEF', $valuePointer = 'vDEF'): array
is now
FlexFormTools->convertFlexFormContentToArray(string $flexFormContent): array
.
The method name is unchanged, but the method signature has been simplified.
FlexFormService->convertFlexFormContentToSheetsArray(string $flexFormContent, string $languagePointer = 'lDEF', string $valuePointer = 'vDEF'): array
is now
FlexFormTools->convertFlexFormContentToSheetsArray(string $flexFormContent): array
.
Again, the name is identical, but the parameters have been reduced.
The helper method
FlexFormService->walkFlexFormNode()
has been made a
private method within
FlexFormTools
.
Impact
Instantiating or injecting
FlexFormService
remains possible in TYPO3 v14 due to a maintained class alias for backward
compatibility.
This alias will be removed in TYPO3 v15.
Affected installations
Any extensions or TYPO3 installations using
FlexFormService
are affected.
The extension scanner will automatically detect these usages.
Migration
Extensions typically did not use the now internal helper method
walkFlexFormNode()
.
In the unlikely case this private method was used, its functionality must now be
implemented within the consuming extension.
The methods
convertFlexFormContentToArray()
and
convertFlexFormContentToSheetsArray()
have lost their second and third
arguments.
These parameters (
lDEF
and
vDEF
) were already fixed internally in
TYPO3 and could no longer be changed, so their removal has no functional impact.
To continue using these methods, extensions should inject
FlexFormTools
instead of
FlexFormService
.
For extensions that need to remain compatible with both TYPO3 v13 and v14, it is
still possible to use
FlexFormService
for
now.
However, when adding compatibility for TYPO3 v15 (and dropping TYPO3 v13),
extensions must switch fully to
FlexFormTools
.
Breaking: #108054 - Enforce explicit opt-in for TypoScript/TSconfig callables
To strengthen TYPO3's security posture and implement defense-in-depth
principles, a new hardening mechanism has been introduced that requires
explicit opt-in for methods and functions that can be invoked through
TypoScript configuration.
The new PHP attribute
#[\TYPO3\CMS\Core\Attribute\AsAllowedCallable]
must be applied to
any method that should be callable via:
TypoScript
userFunc
processing (including the
USER
and
USER_INT
content objects)
TypoScript
stdWrap
functions
preUserFuncInt
,
postUserFunc
and
postUserFuncInt
TypoScript constant comment user functions
TSconfig
renderFunc
in suggest wizard configuration
This security enhancement implements strong defaults through explicit
configuration, following the principle of least privilege.
Implementation details:
New
\TYPO3\CMS\Core\Attribute\AsAllowedCallable
PHP attribute
New
\TYPO3\CMS\Core\Security\AllowedCallableAssertion
service
for validation
Extension code that provides custom processing methods callable from
TypoScript or TSconfig will fail with a
\TYPO3\CMS\Core\Security\AllowedCallableException
if the target
method is not explicitly marked with the
#[AsAllowedCallable]
attribute.
The error message will be:
Attribute TYPO3\CMS\Core\Attribute\AsAllowedCallable required for
callback reference: ["VendorName\\ExtensionName\\ClassName","methodName"]
Copied!
Affected installations
Scenarios using:
custom processing via TypoScript userFunc
custom processing via TypoScript constant comments
custom suggest wizard rendering via TSconfig renderFunc
Migration
Add the
#[AsAllowedCallable]
attribute to all methods that should
be callable from TypoScript or TSconfig.
The implementation of CSS and JavaScript asset concatenation and
pre-compression has been removed from the TYPO3 Core in v14.
The following TypoScript options are now obsolete:
Config property
config.compressCss
Config property
config.compressJs
Config property
config.concatenateCss
Config property
config.concatenateJs
The
resource
properties
disableCompression
and
excludeFromConcatenation
in the
PAGE
properties
includeCSS
,
includeCSSLibs
,
includeJS
,
includeJSFooter
,
includeJSFooterlibs
and
includeJSLibs
.
The configuration option
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
is obsolete
in combination with Breaking: #107943 - Frontend and backend HTTP response compression removed. Existing settings
in settings.php configuration files are automatically removed
when the install tool is first used after upgrading to TYPO3 v14.
The PHP class
\TYPO3\CMS\Core\Resource\ResourceCompressor
has
been removed.
Feature rundown
In TYPO3 versions prior to v14, the system included a built-in mechanism
to compile multiple registered CSS and JavaScript files into single files
and to prepare compressed versions of those concatenated files for
direct delivery by the web server.
This functionality had to be explicitly enabled by setting the global
configuration option
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
and the
TypoScript options
config.concatenate*
and optionally
config.compress*
. CSS and JavaScript files registered in the
frontend using the
PAGE.include*
options were then
concatenated and optionally compressed into a single file.
The implementation also concatenated external asset resources referenced
via http and https, which were fetched server-side using
GeneralUtility::getUrl()
and merged with local resources.
Concatenation was designed to be as transparent as possible: hashes were
created from all referenced file names and their contents (including
external content) for each TYPO3 frontend page request, generating
unique file names that changed whenever an included file or its content
was modified. The resulting concatenated files were then referenced in
the generated HTML page instead of the original resource links.
Pre-compression operated on top of concatenation: if enabled, a gzip-
compressed .gz file was created and referenced in the HTML page
response.
Configuration was handled via TypoScript, while asset registration could
be performed using TypoScript or PHP with the
PageRenderer
. The
Fluid asset ViewHelpers
f:asset.css
and
f:asset.script
register assets using the
AssetCollector
, introduced in TYPO3
v10. This implementation never supported concatenation or compression,
as it was developed independently from the PageRenderer's asset handling
and did not use the
ResourceCompressor
.
Concatenation and compression removal reasoning
A closer look at the concatenation and compression functionality reveals
several reasons for its removal:
HTTP/2 and HTTP/3: Modern HTTP versions allow multiple resource
requests in parallel ("multiplexing"), making server-side asset
concatenation obsolete. These versions also provide significant
performance improvements on both the server and client sides compared
to HTTP/1.1 with concatenation. HTTP/2 and HTTP/3 are only available
via SSL (HTTPS), which is now the standard for all serious websites.
All major web servers and browsers have supported at least HTTP/2 for
years.
Fragile implementation: Concatenating multiple CSS files within
the application caused path and encoding issues. The resulting files
had to be stored in writable public directories, and relative paths in
CSS files (such as those in
@import
rules) had to be parsed and
adjusted. Additionally, the CSS statement
@charset
had to be
parsed since it must appear only once per CSS file, leading to
potential collisions that have never been resolved.
Parallel systems: Concatenation and compression were supported
only for assets registered via TypoScript
page.include*
or the
PageRenderer
in PHP. The Fluid ViewHelpers
f:asset.css
and
f:asset.script
operated independently
and never supported these features. Removing concatenation and
compression simplifies future unification of both asset systems.
Performance issues with external assets: To create as few asset
resources as possible, external assets (example:
https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js)
were by default (if
excludeFromConcatenation = 1
was not
explicitly set) fetched by TYPO3 when creating the page. The hashed
resource path and the file content were part of the created
concatenation filename. This ensured that the server-side created file
was always current. TYPO3 therefore fetched external resources for
each uncached page request. Worse, it fetched those external
resources for each request that contained a non-cached content
element ("INT" elements), and also for each request when an instance
enabled CSP handling. This could easily lead to severe performance
degradation.
Compression dependency: Pre-compression (gzip) was only available
when concatenation was enabled, adding further complexity and
limitations.
HTTP violations with caching: When TYPO3 cached a page that
referenced pre-compressed assets, it stored the version based on the
client’s Accept-Encoding header. Subsequent requests from
clients not supporting gzip would still receive references to
compressed assets, violating HTTP standards.
Double compression risk: Web servers might automatically re-
compress already compressed files. TYPO3 tried to prevent this with an
Apache-specific .htaccess configuration, but other servers
like nginx required custom setups, often confusing integrators.
Modern compression standards: Modern web servers and browsers
support more efficient algorithms such as Brotli and Zstandard, which
TYPO3 never implemented due to the complexity of its existing system.
Minimal performance gain: The benefit of pre-compression was
minor. Modern web servers can compress small assets (like typical CSS
or JS files) on the fly with negligible overhead. As a rough ballpark
estimation, a single CPU core can compress hundreds of MB per second,
while a large JavaScript library like jQuery is usually below 100 KB.
CPU time is rarely the bottleneck.
In summary, asset concatenation and pre-compression are no longer needed
and should not be handled at the application level. The implementation
was riddled with issues over the years and integrators struggled to
reach working solutions. The Core team closed issues in this area for
years with "Won't fix, use a different solution."
Alternatives
Most TYPO3 instances can operate perfectly well without the removed
asset concatenation and compression features. Modern browsers, web
servers, and HTTP protocol versions provide efficient alternatives that
make the previous TYPO3-internal implementation unnecessary.
If your instance still relies on concatenated or pre-built asset bundles
for specific use cases, consider the following alternatives:
Use a modern bundler: Tools such as Vite
or Webpack can handle asset concatenation, minification, and
optimization during the build process. A TYPO3-specific integration is
available as an extension:
Vite AssetCollector.
Consider the `sgalinski/scriptmerger` extension: This community
extension provides an alternative approach to script and stylesheet
merging and compression. It may be useful for projects that cannot yet
switch to build-time bundling.
Enable HTTP/2 or HTTP/3: Modern HTTP versions support
multiplexing, allowing browsers to download multiple assets
simultaneously from a single connection, eliminating the need for
server-side concatenation.
# Enable HTTP/2 on your SSL virtual host
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
# Serve pre-compressed assets if available
gzip_static on;
# Optionally also enable on-the-fly compression
gzip on;
gzip_types text/css application/javascript;
[...]
}
Copied!
Both configurations ensure that compressed versions of static assets
(e.g., .js.gz or .css.gz) are automatically delivered
to clients that support gzip encoding.
Both Apache and nginx can also cache the compressed output in memory
or on disk to avoid runtime overhead. The keywords to look for are
mod_deflate with mod_cache for Apache, and
proxy_cache for nginx.
Use a Content Delivery Network (CDN): For TYPO3 instances
experiencing heavy frontend traffic or high asset load, a CDN can
serve static resources such as CSS, JavaScript, and images directly
from distributed edge servers. This offloads delivery from the main
web server, reduces latency, and improves caching efficiency.
In summary, most TYPO3 setups can safely rely on HTTP/2, modern build
pipelines, and proper web server or CDN configuration to achieve optimal
frontend performance without any TYPO3-internal concatenation or
compression.
Impact
The configuration toggles mentioned above are now obsolete, and TYPO3
will no longer concatenate or compress included assets.
Affected installations
Instances that configured
$GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']
to non-zero
values and enabled the TypoScript settings described above for asset
concatenation or compression are affected.
Migration
Consider one or more of the alternatives outlined above.
Breaking: #108055 - Removed PageRenderer related hooks and methods
no longer receive the array keys
compress
or
excludeFromConcatenation
inside the following data array keys:
jsFiles
jsInline
jsLibs
cssFiles
cssInline
cssLibs
Impact
Calling any of the removed methods listed above will raise PHP fatal errors.
Registrations for removed hooks are no longer executed. Submitting ignored
arguments has no effect anymore, and hook consumers receive slightly different
data from the TYPO3 Core due to removed TypoScript configuration values.
Affected installations
Instances with extensions dealing with low level asset manipulation may be
affected. The extension scanner will find affected extensions when they call
removed methods or hooks.
Migration
There is no direct one-to-one migration in this case.
In general, extensions must no longer expect the existence of code related to
the TypoScript configuration options
config.compressCss
,
config.compressJs
,
config.concatenateCss
,
config.concatenateJs
, and the
resource
properties
disableCompression
and
excludeFromConcatenation
.
The removed hooks and "handlers" can be turned into listeners of:
Existing hook registrations of these three should check whether the
implementations access the array keys
compress
and
excludeFromConcatenation
and avoid doing so. If required, affected code
may need to determine TypoScript options from the
$GLOBALS['TYPO3_REQUEST']
request attribute
frontend.typoscript
directly.
Another alternative is to avoid hook usage altogether by turning the
implementations into PSR-15 middlewares instead.
Breaking: #108084 - Allow rootless paths in URI implementation
Previously, the TYPO3 implementation of
UriInterface
always prefixed rootless paths (paths without a preceding slash) with a slash.
With this normalization in place, it was impossible to represent rootless
paths. This has now changed so that a slash is only prepended to the path
when an authority (host name) is present.
Example
Input: rootless/path/
Examples with different URIs
useTYPO3\CMS\Core\Http\Uri;
$uri = new Uri('rootless/path/');
$uriAsString = (string)$uri;
// before: /rootless/path/// after: rootless/path/// Same behavior with authority
$uri = (new Uri('https://example.com'))->withPath('rootless/path/');
$uriAsString = (string)$uri;
// before: https://example.com/rootless/path/// after: https://example.com/rootless/path/// Colon in first path segment
$uri = new Uri('rootless:path/to/resource');
$uriAsString = (string)$uri;
// before: /rootless:path/to/resource/// after: ./rootless:path/to/resource/
Copied!
Impact
Regarding top level TYPO3 API and functionality, nothing has changed. Required
TYPO3 code has been adapted.
Third party code that uses the
Uri
class
directly will get different results when representing rootless paths without
authority. Code that relied on the normalization done by TYPO3 before is
likely to break.
Since TYPO3 is always dealing with absolute paths, due to URL rewriting in the
backend and the frontend, it is unlikely that much third party code relies on
relative paths, so the impact is expected to be low.
Affected installations
Third party code that is using the
Uri
class
directly and that is representing rootless paths without authority.
Breaking: #108093 - Add respectSubmittedDataValue argument in password ViewHelper
The
<f:form.password>
ViewHelper now provides the argument
respectSubmittedDataValue
, which allows configuration of whether a
submitted field value will be put into the HTML response of the form on
validation errors after submission. The default value of the new argument is
set to
false
, resulting in a submitted field value being cleared on
validation errors of the form.
Impact
A submitted password will not remain as the value for the password field if
form validation fails.
Affected installations
TYPO3 instances using the
<f:form.password>
ViewHelper.
Migration
If the submitted field value of the
f:form.password
ViewHelper must
remain on validation errors of the form, users must adapt the password
ViewHelper usage as shown below:
Class
\TYPO3\CMS\Core\Mail\MailMessage
is a data object that
should not contain service methods like
send()
. The following methods
have been removed from this class:
send()
isSent()
Impact
Using the removed methods on instances of this class will raise fatal PHP
errors.
Affected installations
Instances that create
MailMessage
objects
and call
send()
or
isSent()
are affected. The extension scanner
is not configured to find affected code since the method names are too generic.
Migration
The service (usually a controller class) that sends emails should be
reconfigured to get an instance of
MailerInterface
injected and should use that service to call
send()
.
Example before:
useTYPO3\CMS\Core\Mail\MailMessage;
final readonly classMyController{
publicfunctionsendMail(){
$email = new MailMessage();
$email->subject('Some subject');
$email->send();
}
}
Copied!
Example after:
useTYPO3\CMS\Core\Mail\MailMessage;
useTYPO3\CMS\Core\Mail\MailerInterface;
final readonly classMyController{
publicfunction__construct(
private MailerInterface $mailer
){}
publicfunctionsendMail(){
$email = new MailMessage();
$email->subject('Some subject');
$this->mailer->send($email);
}
}
Copied!
Breaking: #108113 - Globals _GET and _POST not reset to current Request data anymore
The frontend and backend application chain roughly splits like this:
Bootstrap
Create Request object from globals
Start application
Run middleware chain
Run RequestHandler to create a Response by calling controllers (backend) or
ContentObjectRenderer
(frontend)
There was old compatibility code in
RequestHandler
that reset the PHP global variables
_GET
,
_POST
,
HTTP_GET_VARS
and
HTTP_POST_VARS
to values that may have been
written to their Request object counterparts by middlewares.
This backwards compatibility layer has been removed.
Additionally, in frontend rendering, the global variable
$GLOBALS['TYPO3_REQUEST']
is no longer populated within the
PrepareTypoScriptFrontendRendering
middleware. It is now set later in
RequestHandler
.
$GLOBALS['TYPO3_REQUEST']
itself is another compatibility layer that the
TYPO3 Core aims to phase out over time.
Impact
The impact is twofold:
Some TYPO3 Core middlewares manipulate the Request object's "GET"
parameter list (
$request->getQueryParams()
) to, for example,
resolve the frontend slug into the page uid. This is itself a
backwards compatibility layer. Frontend-related code can no longer
expect these manipulated variables to exist in the globals
_GET
,
_POST
,
HTTP_GET_VARS
and
HTTP_POST_VARS
.
Middlewares that are executed after
PrepareTypoScriptFrontendRendering
(middleware key typo3/cms-frontend/prepare-tsfe-rendering)
can no longer rely on
$GLOBALS['TYPO3_REQUEST']
being set.
Affected installations
Instances running code that relies on the removed compatibility layers
may fail or lead to unexpected results.
Migration
Middlewares receive the Request object directly and should use it instead
of fetching it from
$GLOBALS['TYPO3_REQUEST']
. Services triggered by
middlewares that rely on the Request should have it passed in explicitly.
One example frequently used in middlewares is
ContentObjectRenderer
:
Code should in general never rely on the globals
_GET
,
_POST
,
HTTP_GET_VARS
and
HTTP_POST_VARS
. Request-related
state should always be fetched from the Request object. Note that the helper
method
GeneralUtility::getIndpEnv()
will also be phased out once the
TYPO3 Core has removed its last remaining usages.
Breaking: #108114 - Global frontend content link prefixing removed
The frontend rendering contained logic that searched for links to resources
within the generated Response content to globally replace them with the
configured URL prefix (TypoScript
config.absRefPrefix
).
This solution has always been brittle and has now been obsoleted with the
introduction of the System Resource API.
The global search and replace code has been removed, which also obsoletes
setting
$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalAbsRefPrefixDirectories']
.
Impact
Generated content can no longer expect links to resources to be globally
"fixed". They must create the final URL themselves.
The obsolete
$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalAbsRefPrefixDirectories']
will be automatically removed from settings.php after upgrading to
TYPO3 v14 and accessing the install tool.
Affected installations
The System Resource API is integrated into TYPO3 in a way that extensions
usually create proper links automatically as long as the provided Core API is
used.
Instances with extensions that hard code link generation may be affected.
Migration
Instances should check the rendered frontend for broken links after upgrading
to TYPO3 v14 and update hard coded link generation to use proper API calls,
for example based on the various URL, URI and asset-related Fluid ViewHelpers.
This change depends heavily on the specific extension code. There is no general
advice that covers all possible cases for extension developers.
Breaking: #108148 - CDATA sections in Fluid templates no longer removed
Previous versions of Fluid and TYPO3 removed code wrapped in <![CDATA[ ]]>
from template files altogether. This meant that it was possible to use CDATA
to comment out template code. This is no longer possible, since CDATA
sections are now interpreted by Fluid in a different way; see
Feature: #108148 - Alternative Fluid Syntax for CDATA Sections.
Impact
<![CDATA[ ]]> can no longer be used to comment out code in Fluid template
files.
Affected installations
Installations that contain Fluid templates using <![CDATA[ ]]> to comment
out code are affected. A deprecation has been written to the deprecation log
since TYPO3 13.4.21 if this construct is encountered in a Fluid template
during rendering.
Migration
To comment out code in Fluid templates, the
Comment ViewHelper <f:comment>
should be used. Since TYPO3 v13, potential Fluid syntax errors are ignored by
this ViewHelper, which allows commenting out invalid Fluid syntax safely.
With Fluid 5, it is no longer possible to define custom template variables
that start with an underscore (_). These variable names are reserved for
future internal use by Fluid itself, similarly to the already existing
{_all}.
Impact
This change affects ViewHelpers that define new variables, such as:
It also affects Fluid's PHP APIs, namely
$view->assign()
and
$view->assignMultiple()
.
Affected installations
Installations with Fluid templates that use custom variable names starting
with an underscore (_) will encounter exceptions when such a template is
rendered. A deprecation has been written to the deprecation log since
TYPO3 13.4.21 if this is encountered in a Fluid template during rendering.
For template files already using the new *.fluid.* file extension, the built-in
template analyse command will discover affected template files:
vendor/bin/typo3 fluid:analyse
Copied!
For each affected template, the command will output an error like this:
[ERROR] packages/myext/Resources/Private/Templates/Test.fluid.html: Variable identifiers cannot start with a "_": _myvariable
Copied!
Migration
The following examples no longer work with Fluid 5:
#1756622558 TYPO3Fluid\Fluid\Core\Variables\InvalidVariableIdentifierException
Variable identifiers cannot start with a "_": _myVariable
Copied!
In all cases, the variable name must be changed to no longer start with an
underscore (_).
Note that this only affects variable names, not property names in objects or
array keys that are accessed within a Fluid template. The following examples
are not affected by this change:
{myArray._myKey}
{myObject._myProperty}
Copied!
Also note that the existing {_all} (and any further internal variables added
by Fluid) are not affected. This code will continue to work:
<f:renderpartial="Footer"arguments="{_all}"/>
Copied!
Breaking: #108148 - Strict Types in Fluid ViewHelpers
With Fluid 5, various changes have been made to use stricter types in the
context of ViewHelpers. This has consequences in three areas:
Validation of arguments passed to ViewHelpers
(see #1194 on GitHub)
Passing null values to ViewHelpers that generate a HTML tag, also
known as tag-based ViewHelpers
(see #1233 on GitHub)
Required type declarations for custom ViewHelper implementations
(see #1219 on GitHub)
Impact
ViewHelper argument validation
Fluid ViewHelpers now use stricter validation for their arguments by default.
The previous argument validation had numerous blind spots, which meant that
ViewHelper implementations couldn't really rely on the type requirements
specified in the ViewHelper's API. The new implementation performs a stricter
validation, which means that Fluid might reject arguments passed to ViewHelpers
that were previously considered valid (but which the ViewHelper in question
usually didn't know how to handle). The new implementation does however deal
with simple type conversions automatically, so that a ViewHelper that requires
a
string
still can receive an
int
as input.
For integrators, this change might reject certain ViewHelper arguments that
were previously valid, but not covered by the ViewHelper's specified API.
For developers of custom ViewHelpers, this change allows to get rid of custom
validation logic that was previously necessary due to Fluid's spotty
validation.
Note that the
Argument ViewHelper <f:argument>,
which can be used to define an API for a template, is not affected by this
change, as it already used the improved validation from the beginning.
Passing
null
to tag-based ViewHelpers
Previously, Fluid's
TagBuilder
class, which is used to create HTML tags
in tag-based ViewHelpers, treated
null
values as empty strings, leading
to an HTML tag with an empty HTML attribute. With Fluid 5,
null
values lead
to the HTML attribute being omitted from the resulting HTML tag.
In most cases, the impact of this change is non-existent. However, there are
some edge cases where this change is relevant. In TYPO3 Core, the
<f:image>
ViewHelper needed to be adjusted to always render the
alt
attribute,
even if its internal value is
null
, to match the previous output and
to produce valid HTML code.
TYPO3 Core ships with the following tag-based ViewHelpers:
<f:media>
and
<f:image>
<f:asset.css>
and
<f:asset.script>
<f:form>
and
<f:form.*>
<f:link.*>
, except for <f:link.typolink>, which uses TypoScript internally
<f:be.link>
<be:link.*>
<be:thumbnail>
Type declarations in ViewHelper classes
Fluid's ViewHelperInterface now requires proper return types for all ViewHelper
methods. Thus, custom ViewHelper implementations need to be adjusted accordingly.
This is backwards-compatible to previous TYPO3 versions.
Affected installations
All installations need to verify that
ViewHelpers aren't called with invalid argument types
null
values passed to tag-based ViewHelpers don't lead to unexpected
HTML output
Custom ViewHelper implementations need to make sure that they declare
proper return types in the ViewHelper class to conform to Fluid 5's
interface changes, for example:
initializeArguments() must specify void as return type
render() should specify a return type other than void. Even though a
specific type is recommended, it is not required, and mixed can be used as well.
Note that properties in ViewHelper classes are not affected.
The following example doesn't need to be adjusted, no types can/should
be specified for these properties:
Unfortunately, the other changes concern runtime characteristics of
Fluid templates, as they depend on the concrete values of variables that
are passed to a template. Thus, it is not possible to scan for affected
templates automatically.
However, the majority of issues these changes in Fluid might uncover in
existing projects would have already been classified as a bug
(in the template or extension code) before this Fluid change, such as
passing an array to a ViewHelper that expects a string.
Breaking: #108277 - Remove superfluous CacheHashCalculator public methods
Calling the removed methods will result in a fatal PHP error.
Both methods were only used internally for testing purposes and were not part
of the public API contract.
Affected installations
TYPO3 installations that used these methods directly in custom code are
affected. However, this is highly unlikely as they were intended for internal
testing only.
Migration
Instead of modifying configuration after instantiation using the removed
setConfiguration()
method, merge configuration arrays before creating
the
CacheHashConfiguration
instance, then pass it to the
CacheHashCalculator
constructor.
Example:
// Before (removed approach):
$subject = new CacheHashCalculator(
new CacheHashConfiguration($baseConfiguration),
$hashService
);
$subject->setConfiguration([
'cachedParametersWhiteList' => ['whitep1', 'whitep2'],
]);
// After (correct approach):
$configuration = new CacheHashConfiguration(
array_merge($baseConfiguration, [
'cachedParametersWhiteList' => ['whitep1', 'whitep2'],
])
);
$subject = new CacheHashCalculator($configuration, $hashService);
Copied!
Breaking: #108304 - Populate extension title from composer.json
To avoid reading the legacy ext_emconf.php file even in
Composer mode, the extension title is now optionally pulled
from the composer.json description. If the character sequence
- (space, dash, space) is present in description field
in composer.json, then everything before this sequence
is used as title of the extension and the second part
is used as extension description. Note that only the first
occurrence of this sequence is evaluated, so it remains
possible to have this inside the extension description if required.
Impact
Extensions not having their title incorporated in the
composer.json description field, will be shown with the
full description in extension manager and from command
line with typo3 extension:list command.
Affected installations
Installations having custom extensions, where the title
is not part of the description in composer.json
Migration
Put the desired extension title into the composer.json description field
and separate it from the description with - (space, dash, space),
or use the extension manager "Composer Support of Extensions" to get
a suggestion for updating composer.json files accordingly.
All TYPO3 core extensions have set their description in composer.json
accordingly already.
Example of description with title in composer.json:
Extension detection in classic mode now requires a valid composer.json
file instead of ext_emconf.php. The composer.json file must
include "type": "typo3-cms-*" and the extension key in
extra.typo3/cms.extension-key.
Impact
Extensions without a valid composer.json are no longer detected
and loaded in classic mode installations.
Affected installations
All classic mode installations must verify that every extension contains
a composer.json with:
"type" starting with "typo3-cms-"
"extra.typo3/cms.extension-key" containing the extension key
Composer-based installations are not affected.
Migration
Extension authors must ensure their extensions include a valid
composer.json. TER extensions have required this since 2021.
The former page type "External link" has been renamed to "Link" and now fully
supports all typolink capabilities.
It can be used, for example, to configure pages linking to:
external URLs
other pages
content elements or sections
files and folders
email addresses
telephone numbers
custom records, for example news records
Such pages can be displayed in menus and linked from the link wizard of type Page.
The upgrade wizard "Migrate links of pages of type link." automatically migrates
pages of the former type "External URL".
The page type "Shortcut" remains unchanged.
Impact
Editors and integrators can now use the updated page type "Link" to create
menus that contain any link type supported by typolink - including section
links for anchor-based navigation, email addresses, telephone numbers, or
custom record links (for example news records).
Feature: #92760 - Configurable timezone for DateViewHelper
Translation domains have been introduced as a shorter alternative to
file-based references for label resources (.xlf XLIFF files). The
syntax uses the format package[.subdomain...].resource and is fully backward
compatible with existing LLL:EXT: references. Package refers to the
extension key, such as "backend" for "EXT:backend".
This syntax is designed to improve readability, remove explicit references to
file extensions, and provide convenience for new developers and integrators.
The previous locallang.xlf convention has been replaced with a more
generic "messages" resource name, following common conventions in other
localization systems (for example Symfony). This is also where the term
translation domain originates.
The existing syntax and naming
(LLL:EXT:extension/Resources/Private/Language/locallang.xlf:label)
will remain available without deprecation for a long time.
Translation Domain Format
The format defines two parts: the package part (extension key) and the
resource part, separated by a dot.
The resource part omits historical namings such as locallang.xlf and the
locallang_ prefix. The actual label identifier is separated by a colon.
Format
Example usage of "package.resource:identifier"
$languageService->sL('backend.toolbar:save');
// Resolves to: EXT:backend/Resources/Private/Language/locallang_toolbar.xlf// and returns the translated "save" identifier.
Copied!
Domain Resolution
Deterministic File-Based Mapping
Translation domains are resolved deterministically by scanning the file
system. When a domain is first requested for a package:
All label files in Resources/Private/Language/ are discovered.
A domain name is generated from each file name.
The domain-to-file mapping is cached in cache.l10n.
Subsequent requests use the cached mapping.
This ensures that domain names always correspond to existing files and avoids
speculative file system lookups.
When there are filename conflicts such as locallang_db.xlf and
db.xlf, then locallang_db.xlf will be ignored.
Performance Characteristics
The implementation reduces file system operations compared to traditional
file-based lookups, as all label files within an extension are discovered once.
Domain Generation Rules
Domain names are generated from file paths using these transformation rules:
The base path Resources/Private/Language/ is omitted.
The domain name messages is currently evaluated for both the
legacy file name locallang.xlf but also for new files messages.xlf.
If a file messages.xlf is present, this means the locallang.xlf
will never be automatically evaluated for the resulting messages domain.
It is recommended to avoid having both files in the same directory, unless
both contain the same label contents, as no merging of these two
files is performed.
Usage
The translation domain system integrates with the existing
\TYPO3\CMS\Core\Localization\LanguageService
API. Both domain-based and
file-based references are supported:
Domain-based references are shorter and reveal less implementation detail than
full file paths.
CLI Command
The development command
bin/typo3 language:domain:list
lists all available
translation domains along with their available translations and label counts:
# List domains in active extensions
php bin/typo3 language:domain:list
# Filter by extension
php bin/typo3 language:domain:list --extension=backend
The Labels column displays the number of translatable labels within the
English source file.
On top of this, the development command
bin/typo3 language:domain:search
can be used to search for specific label contents. Both commands are provided
in the EXT:lowlevel extension.
PSR-14 Event
The event
\TYPO3\CMS\Core\Localization\Event\BeforeLabelResourceResolvedEvent
is dispatched after domain generation, allowing customization of domain names.
The event provides these public properties:
$packageKey
— The extension key (read-only).
$domains
— An associative array mapping domain names to label files
(modifiable):
array<string, string>
.
Example
Event listener implementation:
namespaceMyVendor\MyExtension\EventListener;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Core\Localization\Event\BeforeLabelResourceResolvedEvent;
final readonly classCustomTranslationDomainResolver{
#[AsEventListener(identifier: 'my-extension/custom-domain-names')]publicfunction__invoke(BeforeLabelResourceResolvedEvent $event): void{
if ($event->packageKey !== 'my_extension') {
return;
}
// Use file my_messages.xlf even if locallang.xlf is found
$event->domains['my_extension.messages'] =
'EXT:my_extension/Resources/Private/Language/my_messages.xlf';
}
}
Copied!
Backend modules
Previously, backend module labels (including their title and description) were
defined in a file like this:
The naming for the short-hand translation domain for modules should follow
the following pattern as best practice:
<extensionkey>.modules.<modulename> - when multiple modules exist for an extension.
Both extensionKey and modulename should use lower snake case ("some_long_module_name"),
ideally without underscores (qrcode.modules.generator is more readable than
qrcode.modules.backend_image_generator for example). Files are put into
EXT:extensionkey/Resources/Private/Languages/Modules/modulename.xlf.
<extensionkey>.module - single backend module only
The file is saved as EXT:extensionkey/Resources/Private/Languages/module.xlf.
To summarize, the key changes are:
Use a speaking XLIFF file inside /Resources/Private/Languages/Modules (best practice, could be any sub-directory)
Use understandable XLIFF identifiers:
- "title" instead of "mlang_tabs_tab"
- "short_description" instead of "mlang_labels_tablabel"
- "description" instead of "mlang_labels_tabdescr"
Use short-form identifiers ("my_extension.modules.my_module" instead of "LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf")
inside the Backend/Modules.php registration.
All TYPO3 Core backend modules that used the old label identifiers have been migrated to the new syntax, the utilized
files are now deprecated, see deprecation. TYPO3 Core also uses
singular module language containers like workspaces.module instead of workspaces.modules.workspaces.
Impact
Translation domains provide a shorter, more readable alternative to file-based
label references. The implementation uses deterministic file-system scanning
with per-package caching to reduce lookups.
All existing LLL:EXT: file references continue to work. Translation domains
are optional and can be adopted incrementally. Both syntaxes can coexist in
the same codebase. This affects TypoScript, Fluid
<f:translate>
usages, TCA configuration, and PHP code using the
LanguageService
API.
TYPO3 Core will gradually migrate internal references to translation domains
over time, increasing readability—especially in Fluid templates or TCA
definitions.
Image processing in TYPO3 can now configure default and specific formats to
use when images are rendered or converted in the frontend, for example in
Fluid:
Depending on the TYPO3 version, processed images were rendered with
.png (or earlier, .gif or .png) file extensions, as
long as the fileExtension parameter with a fixed output format was not
specified.
This default solution had two major drawbacks:
The default file format was hardcoded and not configurable.
Utilizing new file formats (like webp and avif) required code changes.
This has now been changed with the new configuration option
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imageFileConversionFormats']
.
When resizing or cropping an image with a file extension of jpg, jpeg,
gif, png, or svg (and not setting a specific
fileExtension
target format), those images will retain their
respective file formats.
Otherwise, the file format png is used.
Related configuration options that still apply:
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
Current default:
gif,jpg,jpeg,tif,tiff,bmp,pcx,tga,png,pdf,ai,svg,webp,avif
$GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext']
Current default:
txt,ts,typoscript,html,htm,css,tmpl,js,sql,xml,csv,xlf,yaml,yml
$GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']
Current default:
gif,jpg,jpeg,bmp,png,webp,pdf,svg,ai,mp3,wav,mp4,ogg,flac,opus,webm,youtube,vimeo,avif
These still define, per installation, which files can be uploaded or used as
images.
If a new format like heic becomes supported by the used graphics
engine (for example GraphicsMagick or ImageMagick), system maintainers can add
the file extension to
imagefile_ext
. TYPO3 will then recognize this
format and convert it to the default target format (png by default) when
processing images.
If the format should also be available as a target format, add
'heic' => 'heic'
to
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imageFileConversionFormats']
.
Otherwise, heic images can be selected, but processing will still produce
png.
If all image processing (resizing, thumbnails, PDF previews, etc.) should use
heic, set
'default' => 'heic'
and remove other entries.
Currently, the option cannot be configured via
System > Settings > Configure options ... because it uses
an array syntax. It can be changed manually in settings.php. GUI
support may be added in a future release.
The array notation allows defining a target (thumbnail) file extension for
each original file extension individually using the format
{originalExtension} => {targetExtension}.
This configuration would produce the following behavior:
Fluid image rendering results
<f:imagesrc="somefile.jpg"width="80">
-> renders an 80 px thumbnail from "somefile.jpg" to "somefile.jpg"
(rule: "jpg => jpg")
<f:imagesrc="somefile.gif"width="80">
-> renders an 80 px thumbnail from "somefile.gif" to "somefile.avif"
(rule: "default => avif")
<f:imagesrc="somefile.png"width="80">
-> renders an 80 px thumbnail from "somefile.png" to "somefile.avif"
(rule: "default => avif")
<f:imagesrc="somefile.svg"width="80">
-> renders the original SVG at 80 px width
(rule: "svg => svg")
<f:imagesrc="somefile.pdf"width="80">
-> renders an 80 px PDF thumbnail to "somefile.avif"
(rule: "default => avif")
<f:imagesrc="somefile.heic"width="80">
-> renders an 80 px thumbnail from "somefile.heic" to "somefile.webp"
(rule: "heic => webp")
Copied!
Impact
TYPO3 is now more future-proof regarding new image formats and allows modern
file formats to be used by configuration.
Projects can now specify precisely how each format should be converted or
which default format should be used for processed images.
Feature: #97559 - Support property-based configuration for Extbase attributes
PHP attributes in the Extbase context can now be configured using properties
instead of an array of configuration values. This resolves a limitation that
existed since the introduction of Extbase annotations in TYPO3 v9, where
annotation configuration was restricted: all available options needed to be
defined in a single array.
Since annotations were removed with forge#107229 in favor of PHP attributes,
configuration options can now be defined in a more flexible and type-safe way.
This patch serves as a follow-up to forge#107229 and aims to improve the
attribute configuration mechanism by using constructor property promotion in
combination with strictly typed properties.
To maintain backwards compatibility, the first property of each attribute still
accepts an array of configuration options. However, this behavior is deprecated
and will be removed in TYPO3 v15.0 (see
deprecation notice).
Developers are advised to migrate to single-property configuration when using
PHP attributes in Extbase.
Feature: #98239 - PSR-14 Event to modify form after being built
A new PSR-14 event
\TYPO3\CMS\Form\Event\AfterFormIsBuiltEvent
has been introduced which serves as an improved replacement for the
now removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished']
.
The event provides the
$form
public property.
Example
An example event listener could look like the following:
The Administration > Users module has been extended with a new detail view
for backend user groups, complementing the existing detail view for individual
backend users. This comprehensive view provides administrators with complete
visibility into backend user group configurations and their calculated
properties.
The detail view displays:
Basic information about the backend user group (title, description, and so on)
All assigned subgroups and the full inheritance chain
A complete overview of permissions and access rights from the group and
all inherited subgroups
Calculated and processed TSconfig settings showing the final effective
configuration
Database mount points and file mount access permissions
Module access permissions and workspace restrictions
TYPO3 administrators can now efficiently analyze backend user group
configurations without manually tracing through complex inheritance structures.
This enhanced visibility simplifies permission troubleshooting, security
auditing, and group management by providing a consolidated view of all
calculated permissions and settings in one place, similar to the existing
backend user detail functionality.
Feature: #99409 - New PSR-14 BeforeLiveSearchFormIsBuiltEvent
A new PSR-14 event
\TYPO3\CMS\Backend\Search\Event\BeforeLiveSearchFormIsBuiltEvent
has been added.
To modify the live search form data, the following methods are available:
addHint()
: Adds a single hint.
addHints()
: Adds one or multiple hints.
setHints()
: Sets hints and can be used to reset or overwrite the
current ones.
getHints()
: Returns all current hints.
getRequest()
: Returns the current PSR-7 request.
getSearchDemand()
: Returns the
SearchDemand
used by the live search.
setSearchDemand()
: Sets a custom
SearchDemand
object.
getAdditionalViewData()
: Returns the additional view data set to be
used in the template.
setAdditionalViewData()
: Sets the additional view data to be used in
the template.
Note
The method
setAdditionalViewData()
is useful to provide additional
data to the template without the need to cross-class (XCLASS) the controller.
The additional view data can then be used in an overridden backend template
of the live search form.
The "Create new record" component in the backend, which is accessible in the
Content > List module, has been enhanced to automatically detect and
display all available record types for tables that support sub-schemas
(record types). This improvement simplifies creating specific record types and
eliminates the need to change the type afterward in the editing form, which
could previously lead to invalid record states being stored in the database.
The interface now features automatic record type detection. Tables with
multiple record types are automatically expanded to show all available types
in a collapsible dropdown interface.
The options to create new pages now also show the different page types
(
doktype
) as expandable options when creating new pages "inside" or
"after" an existing page.
To disable direct creation of a specific record type, a new TCA option
['creationOptions']['enableDirectRecordTypeCreation'] is available at
the record type level:
useTYPO3\CMS\Core\Domain\Repository\PageRepository´;
// Disable direct creation of shortcuts
$GLOBALS['TCA']['pages']['types'][(string)PageRepository::DOKTYPE_SHORTCUT]['creationOptions']['enableDirectRecordTypeCreation'] = false;
Copied!
Individual titles for each record type are automatically picked up from the
type-specific
title
configuration in the types section (see
Feature #108027),
falling back to the select item label if no type-specific title is defined.
Additionally, the new PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyNewRecordCreationLinksEvent
allows for complete customization of the creation links.
Impact
For tables that support sub-schemas (multiple record types), the new record
wizard automatically detects all available types and displays them in a
collapsible interface. This includes:
All tables with TCA type fields (such as
sys_file_collection
or
index_config
)
The
pages
table with its different
doktype
values
Extension tables with custom record types
Note
The collapsible interface keeps the view clear while providing access
to all options. Icons and labels are automatically generated for each
record type based on the TCA configuration.
The
ModifyNewRecordCreationLinksEvent
provides complete control over
the creation link structure, allowing extensions to:
Add custom record creation options
Modify existing groups and items
Override icons, labels, and URLs
Create entirely custom wizard interfaces
Data Structure
The event works with a nested array structure representing grouped creation
links:
A new TCA field type called country has been added to TYPO3 Core. Its
main purpose is to use the newly introduced
Country API to
provide a country selection in the backend and use the stored representation in
Extbase or TypoScript output.
TCA Configuration
The new TCA type displays all filtered countries including the configurable
name and the corresponding flag.
Configuration/TCA/tx_myextension_mymodel.php
'country' => [
'label' => 'Country',
'config' => [
'type' => 'country',
// available options: name, localizedName, officialName, localizedOfficialName, iso2, iso3'labelField' => 'localizedName',
// countries which are listed before all others'prioritizedCountries' => ['AT', 'CH'],
// sort by the label'sortItems' => [
'label' => 'asc'
],
'filter' => [
// restrict to the given country ISO2 or ISO3 codes'onlyCountries' => ['DE', 'AT', 'CH', 'FR', 'IT', 'HU', 'US', 'GR', 'ES'],
// exclude by the given country ISO2 or ISO3 codes'excludeCountries' => ['DE', 'ES'],
],
'default' => 'HU',
// When required=false, an empty selection ('') is possible'required' => false,
],
],
required
(bool) - whether an empty selection can be made or not
Extbase usage
When using Extbase Controllers to fetch Domain Models containing properties
declared with the
Country
type, these models can be used with their
usual getters, and passed along to Fluid templates as usual.
usePsr\Http\Message\ResponseInterface;
useTYPO3\CMS\Core\Country\Country;
useTYPO3\CMS\Core\Country\CountryProvider;
useTYPO3\CMS\Extbase\Mvc\Controller\ActionController;
classItemControllerextendsActionController{
// ...publicfunction__construct(
private readonly CountryProvider $countryProvider,
){}
publicfunctionsingleAction(SomeDomainModel $model): ResponseInterface{
// Do something in PHP, using the Country APIif ($model->getCountry()->getAlpha2IsoCode() == 'DE') {
$this->loadGermanLanguage();
}
$this->view->assign('model', $model);
// You can access the `CountryProvider` API for additional country-related// operations, too (ideally use Dependency Injection for this):$this->view->assign('countries', $this->countryProvider->getAll());
return$this->htmlResponse();
}
}
You can use any of the
getXXX()
methods available from the
Country API via
the Fluid
{model.country.XXX}
accessors.
If you use common Extbase CRUD (Create/Read/Update/Delete) with models using a
Country type, you can utilize the existing ViewHelper
f:form.countrySelect within your
<f:form> logic.
Please keep in mind that Extbase by default has no coupling (in terms of
validation) to definitions made in the TCA for the properties, as with other
types like file uploads or select items.
That means, if you restrict the allowed countries via filter.onlyCountries on
the backend (TCA) side, you also need to enforce this in the frontend.
It is recommended to use
Extbase Validators
for this task. If you want to share frontend-based validation and TCA-based
validation non-redundantly, you could use data objects (DO/DTO) or ENUMs for
returning the list of allowed countries:
The type
Country
does not point to a real
Extbase model, and thus has no inherent localization or query logic based on
real records. It is just a pure PHP data object with some getters, and a magic
__toString()
method returning a language label (LLL:...) translation
key for the name of the country (
Country->getLocalizedNameLabel()
).
Here are some examples how to access them and provide localization:
<f:comment>Will show something like "AT" or "DE"</f:comment>
Country ISO2:
{item.country.alpha2IsoCode}
<f:comment>Will show something like "CHE"</f:comment>
Country ISO3:
{item.country.alpha3IsoCode}
<f:comment>Will show something a flag (UTF-8 character)</f:comment>
Country flag:
{item.country.flag}
<f:comment>Will show something like "LLL:EXT:core/Resources/Private/Language/Iso/countries.xlf:AT.name"</f:comment>
Country LLL label:
{item.country}
Actual localized country:
<f:translatekey="{item.country}" /><f:comment>Will show something like "LLL:EXT:core/Resources/Private/Language/Iso/countries.xlf:AT.official_name"</f:comment>
Country LLL label:
{item.country.localizedOfficialNameLabel}
Actual localized official country name:
<f:translatekey="{item.country.localizedOfficialNameLabel}" /><f:comment>Will show something like "Germany" (always English)</f:comment>
{item.country.name}
Copied!
You can use the Extbase
\TYPO3\CMS\Extbase\Utility\LocalizationUtility
in
PHP scope (Controllers, Domain Model) to create a custom getter in your Domain
Model to create a shorthand method:
As mentioned above, since
Country
has no
database-record relations. The single-country relation always uses the 2-letter
ISO alpha2 key (respectively custom country keys, when added via the PSR-14 event
BeforeCountriesEvaluatedEvent
). Thus, queries need to utilize them as string
comparisons:
The default Extbase repository magic method
$repository->findBy(['country' => 'DE'])
will work, too.
TypoScript rendering usage via record-transformation
Database records using country type fields can be rendered with the
TypoScript-based record-transformation rendering (data processor).
You can specify how a field containing a country is rendered in the output
(using the name, the flag icon, specific ISO keys) with regular Fluid logic
then:
Step 1: TypoScript utilizing record-transformation, defining a Homepage.html Fluid template
page = PAGE
page {
# Just an example basic template for your site. The important section starts with `dataProcessing`!100 = FLUIDTEMPLATE100 {
templateName = Homepage
templateRootPaths {
0 = EXT:myextension/Resources/Private/Templates/
}
dataProcessing {
10 = database-query
10 {
as = mainContent
# This table holds for example a TCA type=country definition for a field "country"
table = tx_myextension_domain_model_mycountries
# An extra boolean field "show_on_home_page" would indicate whether these# records are fetched and displayed on the home page
where = show_on_home_page=1
# Depending on your table storage you may need to set a proper pidInList constraint.#pidInList = 4711
dataProcessing {
# Makes all records available as `{mainContent.[0..].myRecord}` in the# Fluid file EXT:myextension/Resources/Private/Templates/Homepage.html10 = record-transformation
10 {
as = myRecord
}
}
}
}
}
}
<f:ifcondition="{mainContent}"><f:foreach="{mainContent}"as="element"><!-- given that your 'tx_myextension_domain_model_mycountries' has a TCA field called "storeCountry":
Selected Country:
<f:translate key="{element.myRecord.storeCountry.localizedOfficialNameLabel}" />
</f:for>
<!-- note that you can access any transformed record type object via 'element', also multiple country
elements could be contained in 'element.myRecord'. --></f:if>
Copied!
Hint
Instead of adding the data processor to the
PAGE
definition, you could
create your own country Content Element type and set it for
tt_content.country, and utilize a Content-Element specific Fluid template
accessing this data, providing something like a "Store" Content Element
associated with a country.
Impact
It is now possible to use a dedicated TCA type for storing a relation to a
country in a record.
Using the new TCA type, corresponding database columns are added automatically.
Country-annotated properties of Extbase Domain Models can be evaluated in
Extbase and via TypoScript.
Feature: #101059 - Allow install tool sessions without shared file system
The default file-based session handling for the Install Tool remains unchanged.
If no alternative session handler for the Install Tool is configured,
the default behavior is used.
Custom session handlers can be created by implementing PHP's
\SessionHandlerInterface
.
Feature: #103258 - Language filter for list module
The list module now provides a language filter in the document header, similar
to the existing language selector in the page module. This allows backend users
to filter records by language, making it easier to focus on content in a
specific language when working with multilingual websites.
The language filter appears as a dropdown button in the document header toolbar
and provides the following options:
All available site languages that have page translations
"All languages" (if translations exist on the current page)
Key behaviors
Language selection persistence
The selected language is stored in the backend user's module data and persists
across page navigation. When switching between pages, the previously selected
language remains active if available on the new page.
Automatic fallback
If navigating to a page where the selected language is not available (no
translation exists), the list module automatically falls back to:
"All languages" mode (if the page has any translations)
The default language (if the page has no translations at all)
This fallback is temporary and does not overwrite the language preference.
When navigating to a page with the selected language translation, it will
automatically be restored.
Display behavior
When a specific language is selected:
Records in the selected language are displayed
Default language records (language 0) are always included as fallback
Records with the "all languages" flag (-1) are included
Example: If French is selected, one will see French translations, default
language content, and content marked for "all languages".
Localization restrictions
The localization panel respects page translation availability. When a language
is selected or when viewing in "all languages" mode, the list module only
offers localization options for languages where the page has an existing
translation.
This ensures data integrity by preventing the creation of records in
languages where the parent page does not exist.
Impact
Backend users can now efficiently filter list module records by language,
improving the workflow when managing multilingual content.
The language selection persists across page navigation, reducing the need to
repeatedly select the same language. The intelligent fallback mechanism ensures
the list module always displays relevant content, even when switching between
pages with different translation availability.
This enhancement brings the list module's language handling in line with that
of the page module, providing a consistent user experience across TYPO3's
backend.
Feature: #103740 - Language selection for backend module "Status - Pagetree Overview"
This change makes it possible to switch the displayed page tree to the selected
language and adjust all labels, as well as edit and view links, accordingly.
The language selection dropdown is located next to the other filter options
(recursion depth, information type) and complements the
Content > Status > Localization Overview module by providing a
page- and record-focused view.
Impact
The Content > Status backend module is now more useful for sites with
multiple languages, offering a quick overview of information for the selected
page and its subpages in the chosen language.
Feature: #104047 - Option to report redirects in link validator
A new Page TSconfig option
mod.linkvalidator.linktypesConfig.external.allowRedirects
has been introduced to the link validator. It allows redirects
(HTTP 3xx responses) to be reported as problems when validating external links.
A new command cache:flushtags has been introduced to allow flushing cache
entries by tag.
Multiple tags can be flushed by passing a comma-separated list of tags.
It is also possible to flush tags for a specific cache group by using the
--groups or -g option. If no group is specified, all cache groups
are considered.
Note that certain combinations of groups and tags do not make sense,
specifically the di and system cache groups.
The Install Tool password can now also be set via the TYPO3 command line interface
instead of only via direct file access. This allows for better automation and
easier hash generation without manual steps.
Usage
Interactively create and write an Install Tool password hash:
vendor/bin/typo3 install:password:set
Copied!
Return the generated password hash without writing to the configuration:
vendor/bin/typo3 install:password:set --dry-run
Copied!
Run without interaction (generates a random password):
The last variation can, for example, be used in CI/CD automation to
generate a suitable password, process the output (the generated password
needs to be persisted separately), and store it in vaults or environment
variables for later use.
Impact
It is now possible to set the hashed Install Tool password using the
TYPO3 command line interface.
Feature: #105549 - Support qualified and unqualified ISO8601 dates in DataHandler
The
DataHandler
API has been extended
to support both qualified and unqualified ISO 8601 date formats, correctly
handling supplied timezone offsets when provided.
Qualified ISO 8601
Includes an explicit timezone offset (for example,
1999-12-11T10:09:00+01:00 or 1999-12-11T10:09:00Z)
Unqualified ISO 8601
Omits timezone offsets, representing LOCALTIME (for example,
1999-12-11T10:09:00)
The
DataHandler
now accepts five
different formats:
Format
Examples
Unqualified ISO 8601
(LOCALTIME)
'Y-m-d\\TH:i:s'
1999-11-11T11:11:11
Qualified ISO 8601
'Y-m-d\\TH:i:sP'
1999-11-11T10:11:11Z
1999-11-11T11:11:11+01:00
DateTime objects
\DateTimeInterface
new \DateTime('yesterday')
new \DateTimeImmutable()
SQL-flavored dates
(internal use)
'Y-m-d H:i:s'
1999-11-11 11:11:11
Unix timestamps
(internal use)
'U'
942315071
The ISO 8601 variants and
\DateTimeInterface
objects are intended for use
in the public API. The SQL-flavored variant and Unix timestamps are primarily
intended for internal operations such as copy or import processes involving
native
DATETIME
and
INT
timestamp database fields.
Passing datetime data via the DataHandler PHP API
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
'tx_myextension_mytable' => [
'NEW-1' => [
'pid' => 2,
// Format as LOCALTIME'mydatefield_1' => $myDate->format('Y-m-d\\TH:i:s'),
// Format with timezone information// (offsets will be normalized to the persistence timezone format,// UTC for integer fields, LOCALTIME for native DATETIME fields)'mydatefield_2' => $myDate->format('c'),
// Pass \DateTimeInterface objects directly'mydatefield_3' => $myDate,
],
],
]);
Copied!
Impact
TYPO3 now provides accurate and consistent handling of ISO 8601 dates,
eliminating previous issues related to timezone interpretation and LOCALTIME
representation.
Feature: #105624 - PSR-14 event after a backend user password has been reset
A new PSR-14 event
\TYPO3\CMS\Backend\Authentication\Event\PasswordHasBeenResetEvent
has been introduced. It is dispatched right after a backend user has reset their password
and it has been hashed and persisted to the database.
The event contains the corresponding backend user UID.
Example
The corresponding event listener class:
<?phpnamespaceVendor\MyPackage\Backend\EventListener;
useTYPO3\CMS\Backend\Authentication\Event\PasswordHasBeenResetEvent;
useTYPO3\CMS\Core\Attribute\AsEventListener;
finalclassPasswordHasBeenResetEventListener{
#[AsEventListener('my-package/backend/password-has-been-reset')]publicfunction__invoke(PasswordHasBeenResetEvent $event): void{
$userId = $event->userId;
// Add custom logic for the backend user UID
}
}
Copied!
Impact
It is now possible to add custom business logic after a backend user has reset
their password using the new PSR-14 event
PasswordHasBeenResetEvent
.
TYPO3 now notifies backend users by email when a failed MFA (multi-factor
authentication) verification attempt occurs. The notification is sent only if
an MFA provider is configured and the user has a valid email address in their
profile.
Impact
TYPO3 backend users now benefit from improved security awareness through
immediate email notifications about failed MFA verification attempts. This
feature is particularly useful in cases where backend accounts with active MFA
configuration are targeted by unauthorized access attempts.
Feature: #105833 - Extended page tree filter functionality
The page tree is one of the central components in the TYPO3 backend,
particularly for editors. However, in large installations, the page tree can
quickly become overwhelming and difficult to navigate. To maintain a clear
overview, the page tree can be filtered using basic terms, such as the page
title or ID.
To enhance the filtering capabilities, the new PSR-14 event
\TYPO3\CMS\Backend\Tree\Repository\BeforePageTreeIsFilteredEvent
has been introduced. This event allows developers to extend the filter
functionality and process the given search phrase in more advanced ways.
Using this event, it is for example possible to evaluate a given URL or to
add additional field matchings, such as filtering pages by their
doktype
or their configured backend layout.
The event provides the following public properties:
$searchParts:
The search parts to be used for filtering
$searchUids:
The uids to be used for filtering by a special search part, which
is added by Core always after listener evaluation
$searchPhrase
The complete search phrase, as entered by the user
$queryBuilder:
The current
QueryBuilder
instance to provide context and to be used to create search parts
Important
The
QueryBuilder
instance is
provided solely for context and to simplify the creation of search parts
by using the
ExpressionBuilder
via
$queryBuilder->expr()
. The instance itself must not be
modified by event listeners and is not considered part of the public API.
TYPO3 reserves the right to change the instance at any time without prior
notice.
Example
The following event listener class demonstrates how to add additional
conditions to the page tree filter using the PHP attribute
#[AsEventListener]
for registration.
useTYPO3\CMS\Backend\Tree\Repository\BeforePageTreeIsFilteredEvent;
useTYPO3\CMS\Core\Attribute\AsEventListener;
useTYPO3\CMS\Core\Database\Connection;
finalclassMyEventListener{
#[AsEventListener]publicfunctionremoveFetchedPageContent(BeforePageTreeIsFilteredEvent $event): void{
// Add an additional UID to the filter
$event->searchUids[] = 123;
// Add evaluation of doktypes to the filterif (preg_match('/doktype:([0-9]+)/i', $event->searchPhrase, $match)) {
$doktype = $match[1];
$event->searchParts = $event->searchParts->with(
$event->queryBuilder->expr()->eq(
'doktype',
$event->queryBuilder->createNamedParameter($doktype, Connection::PARAM_INT)
)
);
}
}
}
Copied!
Impact
With the new PSR-14 event
BeforePageTreeIsFilteredEvent
,
custom functionality and advanced evaluations can now be added to enhance the
page tree filter.
Feature: #106072 - Introduce regex-based replacements for slugs
A second replacement configuration array has been added to support
regular expression (regex)-based definitions. This allows defining
case-insensitive or wildcard replacements for slug generation.
Note
Regular expression replacements are applied before any sanitation
of the field values takes place. This means:
Values are still in their original form - not lowercased or otherwise processed.
The replacements apply only to the fields defined in
['generatorOptions']['fields']
.
No automatic escaping or character detection is performed, so
patterns must be written carefully.
Keep these points in mind when defining regex patterns.
Impact
Slug fields now support a new regexReplacements configuration array
inside generatorOptions.
Example TCA configuration
$GLOBALS['TCA'][$table]['columns']['slug']['config']['generatorOptions']['regexReplacements'] => [
// Case-insensitive replacement of Foo, foo, FOO, etc. with "bar"'/foo/i' => 'bar',
// Remove string wrapped in parentheses'/\(.*\)/' => '',
// Same, using a custom regex delimiter'@\(.*\)@' => '',
];
Copied!
Feature: #106074 - Show editor information in workspace "Publish" module
The "Last changed" column in the Content > Publish module has
been improved. It now displays the username and avatar of the editor who most
recently modified the corresponding record.
The column shows the editor's username in a badge, or Unknown if no editor
information is available. This helps reviewers quickly identify who last worked
on each workspace record.
Impact
Workspace information now reveals the last editor of a record within the
workspace context.
Feature: #106092 - Associative array keys for TCA valuePicker items
It is now possible to define associative array keys for the
items
configuration of the TCA type
valuePicker
.
The new keys are called
label
and
value
.
This follows the change made previously to the
items
configuration
of the TCA types
select
,
radio
, and
check
.
See forge#99739.
Impact
The TCA
items
configuration can now be defined in a more consistent
and readable way using associative array keys. This eliminates ambiguity
about whether the label or value comes first.
Optional keys such as
icon
,
group
, or
description
can be
used as needed.
Feature: #106232 - Provide record title tag provider
The class
\TYPO3\CMS\Core\PageTitle\RecordTitleProvider
introduces a new page title provider with the identifier recordTitle.
It is executed before the
SeoTitlePageTitleProvider
,
which uses the TypoScript identifier seo.
This provider can be used by third-party extensions to set the page title
programmatically.
A new PSR-14 event
\TYPO3\CMS\Redirects\Event\AfterPageUrlsForSiteForRedirectIntegrityHaveBeenCollectedEvent
has been introduced. It allows extensions to register event listeners to modify
the list of URLs processed by the CLI command
redirects:checkintegrity.
Example
The following example shows an event listener that uses the PHP attribute
#[AsEventListener]
to register itself and adds the URLs found in a site's
XML sitemap to the list of URLs checked by the redirects integrity command.
All implementations of
TypolinkBuilderInterface
are
automatically configured as public services in the dependency injection
container, removing the need for manual service configuration.
TypolinkBuilder classes can now use proper dependency injection through
their constructors, improving testability and aligning with TYPO3's
architectural best practices.
The
ServerRequestInterface
is now passed
directly, providing access to the request context without relying on global
state.
The new interface introduces a cleaner separation of concerns
and more explicit parameter passing.
Example usage
Creating a custom TypolinkBuilder using the new interface:
The
FormManagerController
now uses
the
YamlFileLoader
when
creating new forms from templates. This change enables the processing of
placeholders within template files, such as environment variables in the
format
%env(ENV_NAME)%
, as well as the import of other YAML files.
This enhancement allows for more flexible form templates that can adapt to
different environments through environment variable substitution and YAML
imports.
imports:-{resource:'User.yaml'}identifier:contactFormlabel:'Contact Form %env(ENV_NAME)%'type:Formrenderables:-type:Pageidentifier:messagelabel:Messagerenderables:-defaultValue:''type:Textidentifier:text-1label:Subject
Form templates can now contain environment variable placeholders using the
%env(ENV_NAME)%
syntax and import other YAML files. These placeholders
and imports are automatically resolved when new forms are created from the
template.
Feature: #106510 - Add PSR-14 events to Extbase Backend::getObjectCountByQuery method
The class
\TYPO3\CMS\Extbase\Persistence\Generic\Backend
is the central
entity for retrieving data from the database within the Extbase persistence
framework.
Since 2013, the
getObjectDataByQuery()
method has supported events
(previously signals) to allow modification of data retrieval.
In many use cases, especially when used together with
QueryResult
, another key method is
involved:
getObjectCountByQuery()
. This method is frequently used in
combination with Fluid templates.
Until now, extensions or other code using the existing events for data retrieval
could not ensure consistent modification of queries between data retrieval and
counting operations, resulting in mismatched query results.
The
getObjectCountByQuery()
method has now been enhanced with new PSR-14
events, enabling extensions to modify all aspects of query processing within
Extbase's generic
Backend
to achieve consistent results.
The new events are:
ModifyQueryBeforeFetchingObjectCountEvent
Allows modification of the query before it is passed to the storage backend.
ModifyResultAfterFetchingObjectCountEvent
Allows adjustment of the result after the query has been executed.
Typically, an extension should implement these events pairwise:
ModifyQueryBeforeFetchingObjectCountEvent
together with
ModifyQueryBeforeFetchingObjectDataEvent
ModifyResultAfterFetchingObjectCountEvent
together with
ModifyResultAfterFetchingObjectDataEvent
A new ARIA 1.2–compliant combobox web component has been introduced, replacing
the legacy value picker select pattern. The implementation follows the W3C
accessibility guidelines and provides complete keyboard navigation support.
FormEngine elements, including
EmailElement
,
InputTextElement
, and
NumberElement
, have been updated to
use the new combobox component instead of the previous value picker
implementation. The link browser components have also been adapted to use the
combobox pattern.
Impact
The new combobox component offers full keyboard navigation using the arrow keys,
Enter, Tab, and Escape.
It includes visual selection indicators with checkmarks and a clear button
for resetting the input value, improving accessibility and overall usability
in the TYPO3 backend.
Feature: #106686 - Enhance file browsers with column selector
The file browsers used in the file selector (for example, in file fields)
and the file link browser (for example, for RTE links) have been enhanced
to support the column selector component.
This allows backend users to customize which columns are displayed
(for example, "alternative", "timestamp", and so on), improving usability and
aligning the interface with the standard File List module.
This enhancement is particularly useful in large file storages, making it
easier to locate recently updated files or identify large files more quickly.
Impact
The file selector and link browser interfaces now include a column selector.
This improves consistency across TYPO3 backend modules and provides users with
greater control over the visibility of file metadata.
No additional configuration is required for this feature.
Whether the column selector is shown is still determined by the user TSconfig
option
options.file_list.displayColumnSelector
.
Feature: #106739 - Scheduler tasks as native TCA table
For historical reasons, the TYPO3 system extension
typo3/cms-scheduler
used a special mechanism for persisting data to
the database. This was achieved by serializing the entire task object into a
single field of the database table
tx_scheduler_task
.
In TYPO3 v14, this behavior has been reworked. The logic for custom fields has
been split into a dedicated database field
parameters
of type
json
and a new database field
tasktype
that stores the scheduler task name or
CLI command.
An upgrade wizard automatically migrates all existing scheduler tasks to the new
database structure.
Impact
With this change, TCA is now defined for the
tx_scheduler_task
table in
TYPO3. This provides several advantages over the previous implementation:
The editing interface is now handled via FormEngine, making it more flexible
and extensible.
Changes are stored to the database via DataHandler, which allows
customization of persistence operations. The history and audit
functionality is now also available for scheduler tasks.
Database entries of
tx_scheduler_task
can now be exported and
imported using the standard import/export functionality.
Deleted tasks can be restored via the recycler module.
Additional functionality such as support for automated database restrictions and
the TCA schema is available as well.
Feature: #106752 - Add password hashing option to SaveToDatabase finisher
A new option
hashed
has been added to the
SaveToDatabaseFinisher
of the system
extension
typo3/cms-form
.
When saving form data to a database table, setting
hashed: true
for a
field causes the value to be hashed using the default frontend password hashing
mechanism before it is written to the database.
This improves security by preventing passwords from being stored in plain text.
A new CLI command
vendor/bin/typo3 completion
has been added to the
typo3
CLI dispatcher script.
This command enables shell auto-completion for supported shells, allowing
developers to use the Tab key to trigger command and option suggestions.
The
completion
command is provided by the
symfony/console
package and is not a custom implementation.
This ensures compatibility with ongoing improvements in the Symfony ecosystem
and benefits from a broad user base and community support.
Supported shells
The command reports unsupported shells and lists available ones:
# bin/typo3 completion shell
Detected shell "shell", which is not supported by Symfony shell completion
(supported shells: "bash", "fish", "zsh").
Copied!
Installation modes
The command supports two installation modes — static and dynamic.
Run vendor/bin/typo3 completion --help to see detailed usage instructions and
the supported shells (bash, fish, zsh).
Static installation
Dump the completion script to a file and source it manually or install it
globally, for example:
vendor/bin/typo3 completion bash | sudo tee /etc/bash_completion.d/typo3
The
typo3
CLI dispatcher now supports shell auto-completion, improving
the user experience without affecting existing command usage.
This also lays the foundation for further enhancements such as improved
auto-completion for command options and arguments.
The following existing commands already provide completion for their arguments:
redirects:cleanup
redirects:checkintegrity
styleguide:generate
Example
The following example shows how to add auto-completion support to a custom
Symfony console command:
EXT:my_extension/Classes/Command/GreetCommand.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Command;
useSymfony\Component\Console\Attribute\AsCommand;
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Completion\CompletionInput;
useSymfony\Component\Console\Input\InputArgument;
#[AsCommand(
name: 'myextension:greet',
)]
classGreetCommandextendsCommand{
protectedfunctionconfigure(): void{
$this
->addArgument(
'names',
InputArgument::IS_ARRAY,
'Who do you want to greet (separate multiple names with a space)?',
null,
function(CompletionInput $input): array{
// Value already typed by the user, e.g. "myextension:greet Fa"// before pressing Tab — this will contain "Fa"
$currentValue = $input->getCompletionValue();
// Example of available usernames
$availableUsernames = ['jane', 'jon'];
return $availableUsernames;
}
);
}
}
A new dynamic category "Recently used" has been added to the record creation
wizards for the following components:
Content elements
Form elements
Dashboard widgets
This category lists the record or form types that a user has recently selected
in the respective wizard. It remains hidden until at least one record has been
created, keeping the interface uncluttered for new users.
This enhancement allows backend users to quickly access and reuse frequently
used record types, improving workflow efficiency and usability.
Impact
All record wizards now feature a "Recently used" section. This feature is
enabled by default and requires no additional configuration.
Users can manage the display of this category in their personal backend
settings.
This improvement streamlines access to frequently used records, enhancing the
overall editing experience for editors and integrators.
Example
When creating a new content element, users will now see a "Recently used"
section listing previously used content types, making them accessible with
a single click.
Feature: #106945 - Allow usage of Symfony validators in Extbase
Extbase models and controllers now support the use of
Symfony Validators.
Validators are based on Symfony Constraints, which can be added as attributes
to domain model properties and controller methods.
Once a constraint attribute is detected while reflecting properties or methods,
it is decorated by the new
ConstraintDecoratingValidator
class, which is compatible with Extbase's
ValidatorInterface
.
Decorated constraints may include localizable messages. If a message contains
valid LLL: syntax, the label will be translated automatically. The decorating
validator also handles message parameters by converting named parameters such as
{{ value }} into
sprintf
-compatible placeholders like %1$s.
Important
Most available Symfony constraints, such as
#[NotBlank]
and
#[Regex]
, can be used. However, more complex constraints such as
#[File]
or
#[Image]
are not yet compatible with the current
Extbase implementation, as they are closely tied to the Symfony framework.
Compatibility for those constraints may be added in the future.
A wide range of Symfony validators can now be used directly in Extbase.
This provides a more flexible and standardized validation workflow without the
need to implement custom validators, as Symfony already ships with a large
number of predefined constraints.
TYPO3 now automatically includes all fields of suitable types in
backend search operations, e.g., in the List module.
This eliminates the need for the previously used TCA ctrl option
searchFields
, which has been removed.
Instead, a new per-field configuration option
searchable
has been introduced. It allows integrators to fine-tune whether
a specific field should be included in backend search queries.
By default, all fields of supported types are considered searchable.
To exclude a field from being searchable, set the following in the
field’s TCA configuration:
Note that until
searchFields
is manually removed from your TCA, the
automatic TCA migration sets all suitable fields, which are not included
in the
searchFields
configuration, to
searchable => false
to
keep current behavior.
Supported Field Types
The following TCA field types support the
searchable
option and are
automatically considered in searches unless explicitly excluded:
color
datetime
(when not using a custom
dbType
)
email
flex
input
json
link
slug
text
uuid
Unsupported field types such as
file
,
inline
,
password
or
group
are excluded from search and do not support the
searchable
option.
Impact
Backend search becomes more consistent and automatic.
No need to manually maintain a
searchFields
list in TCA.
Integrators have more granular control over search behavior on a field level.
Custom fields can easily be excluded from search using the
searchable
option.
Migration
If your extension previously relied on the
searchFields
TCA option,
remove it from the
ctrl
section and instead define
'searchable' => false
on fields that should be excluded from search results.
No action is needed if the default behavior (search all suitable fields)
is acceptable.
Following forge#106934, which introduced the dynamic Recently used
category in record wizards, the component has been extended to
store the last selected category. When opening a record wizard,
for example, the wizard to create new content elements, it will now
automatically preselect the category that was last used.
This enhancement improves usability and consistency, especially in
installations with many categories, including those added by third-party
extensions.
Impact
Record wizards now automatically preselect the last used category when opened again.
No migration or configuration is required. The feature is enabled by default.
Dashboard widgets can now be configured on a per-instance level using the
Settings API. This allows widget authors to define settings that editors can
modify directly from the dashboard interface, making widgets more flexible and
adaptable to different use cases.
Typical configuration options include URLs for RSS feeds, limits on displayed
items, or categories for filtering content.
Each widget instance maintains its own configuration, enabling multiple
instances of the same widget type with different settings on the same or
different dashboards.
Impact
For editors
Dashboard widgets now display a settings (cog) icon when configuration is supported.
Clicking the icon opens a modal dialog with configurable options.
Settings are applied immediately after saving, and the widget refreshes automatically.
Each widget instance is configured independently per user.
For integrators
Dashboard widgets can now expose user-configurable options.
Settings are validated using the existing Settings API type system.
Includes an upgrade wizard to migrate existing widgets of this type to the new format.
Example
Widget authors can implement configurable widgets by migrating from the
current interface
WidgetInterface
to
the new renderer interface
WidgetRendererInterface
,
which allows defining settings in the widget renderer:
The methods
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin()
and
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin()
have been
extended to accept a FlexForm definition directly via an additional
$flexForm
argument.
This new argument allows extensions to provide the FlexForm data structure when
registering a plugin. The FlexForm can either be a reference to a FlexForm
XML file (for example, FILE:EXT:my_extension/Configuration/FlexForm.xml) or
the XML content itself.
This simplifies configuration and avoids the need to define the FlexForm separately
in TCA.
Internally, this adds the FlexForm definition to the ds option of the plugin
via the
columnsOverrides
configuration and also adds the pi_flexform
field to the showitem list. For more information, see
Breaking: #107047 - Remove pointer field functionality of TCA flex, which describes the migration of the ds
option from multi-entry to single-entry.
FlexFormTools schema parameter requirement
The service
FlexFormTools
has been refactored to remove its dependency on
$GLOBALS['TCA']
, which caused
architectural issues.
The following methods now support an explicit
$schema
parameter that accepts
either a
TcaSchema
object or a raw TCA
configuration array:
getDataStructureIdentifier()
parseDataStructureByIdentifier()
cleanFlexFormXML()
Previously, these methods had no schema parameter and relied on
$GLOBALS['TCA']
internally, which was problematic during schema building.
Calling code must now explicitly provide schema data, either as:
A resolved
TcaSchema
object (for normal usage)
A raw TCA configuration array (for schema building contexts)
This architectural improvement eliminates circular dependencies and allows
FlexFormTools
to be used
during schema building processes where TCA Schema
objects are not yet available, resolving issues in components such as the
RelationMapBuilder
.
useTYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
useTYPO3\CMS\Core\Utility\GeneralUtility;
// In RelationMapBuilder - previously not possible
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
foreach ($tca as $table => $tableConfig) {
foreach ($tableConfig['columns'] ?? [] as $fieldName => $fieldConfig) {
if ($fieldConfig['config']['type'] === 'flex') {
// Can now use raw TCA during schema building
$dataStructure = $flexFormTools->parseDataStructureByIdentifier(
$identifier,
$tableConfig // Raw TCA array
);
}
}
}
Copied!
Impact
Direct FlexForm plugin registration
This enhancement simplifies plugin configuration and FlexForm integration, as
FlexForms can now be registered directly with the plugin. The call
ExtensionManagementUtility::addPiFlexFormValue()
is no longer required.
This method has been deprecated; see Deprecation: #107047 - ExtensionManagementUtility::addPiFlexFormValue().
FlexFormTools schema support
The service now automatically detects the input type and uses the appropriate
resolution strategy for both TCA Schema objects and raw TCA arrays. It no longer
relies on
$GLOBALS['TCA']
, allowing direct control over the service and
making it usable during schema building where no TCA Schema is available.
Technical details
The service uses PHP union types (
array|TcaSchema
) and automatically routes
to the appropriate internal methods. Both input types produce identical normalized
output, ensuring consistent data structures for all
FlexFormTools
consumers.
Feature: #107056 - Introduce headerData and footerData ViewHelpers
Two new Fluid ViewHelpers have been introduced to allow injecting arbitrary
content into the HTML
<head>
or before the closing
</body>
tag
of a rendered page:
<f:page.headerData>
- injects content into the
<head>
section
<f:page.footerData>
- injects content before the closing
</body>
tag
The ViewHelpers internally use the
PageRenderer
API and are useful when existing ViewHelpers such as
<f:asset.css>
or
<f:asset.script>
do not support all required
attributes or use cases (for example,
dns-prefetch
,
preconnect
,
tracking scripts, or inline JavaScript).
<f:page.footerData><script>var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u = "https://your-matomo-domain.example.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '1']);
var d = document,
g = d.createElement('script'),
s = d.getElementsByTagName('script')[0];
g.async = true;
g.src = u + 'matomo.js';
s.parentNode.insertBefore(g, s);
})();
</script></f:page.footerData>
Copied!
Both ViewHelpers output the given content as-is. Any user-supplied input passed
to these ViewHelpers must be escaped manually to prevent Cross-Site Scripting
(XSS) vulnerabilities.
Impact
Extension authors and integrators can now use the new ViewHelpers to add raw
HTML content, such as
<link>
or
<script>
tags, directly into
the rendered page output.
Feature: #107081 - Add options to predefine link target and class in link browser
The link browser now supports preconfiguring link target and class attributes.
This allows administrators to set global defaults for all link types or specific
defaults per handler type, reducing manual selection effort for editors.
This feature improves editor workflows by providing meaningful default values
for link attributes while preserving full flexibility for customization.
The hierarchical configuration system allows both global defaults and
handler-specific overrides, making link creation faster and more consistent.
TYPO3 already uses the native
URL
and
URLSearchParams
objects when
working with URLs. The module
@typo3/core/factory/url-factory.js
has been
introduced to provide a consistent and convenient way to create and manage these
objects.
Impact
URL
and
URLSearchParams
objects can now be created by the factory's
createUrl()
and
createSearchParams()
methods.
The method
createUrl()
creates a full
URL
object and automatically
sets its base. It accepts the following arguments:
url
- string
parameters
- mixed
If provided, the
parameters
value is passed to
createSearchParams()
(described below).
The method
createSearchParams()
creates a
URLSearchParams
object and
accepts the following argument:
parameters
- mixed
Parameters can be passed as plain string values or nested objects.
Passing a plain array is not supported.
Note
When a nested object is passed, values of type
null
or
undefined
are discarded.
Examples
The following examples assume the existence of
TYPO3.settings.ajaxUrls.my_dedicated_endpoint
, pointing to the route
/custom_endpoint, while being on https://localhost for documentation
purposes.