Migrating to TYPO3 13 / PHP 8.3
Note
This guide is for existing extensions only.
If you are creating a new extension with Extension Builder 13.x, the generated code already follows all patterns described here — nothing to do.
This guide is relevant if you have an extension that was originally generated with an older version of the Extension Builder (TYPO3 11 or earlier) and you are now running it on TYPO3 13. The Extension Builder preserves existing code during round-trips and does not automatically update old patterns. The items below describe what needs to be checked and updated manually.
System Requirements
| Component | Required version |
|---|---|
| TYPO3 | ^13.0 |
| PHP | ^8.3 |
| Extension Builder | 13.x branch |
Generated Code Changes
The Extension Builder 13.x branch generates code that is compatible with TYPO3 13 and PHP 8.3. If you have previously generated an extension with an older version, you need to update the following patterns manually or regenerate the affected files.
Repository Classes
Before (TYPO3 11 generated):
class ArticleRepository
{
protected $defaultOrderings = ['sorting' => QueryInterface::ORDER_ASCENDING];
}
After (TYPO3 13 generated):
use TYPO3\CMS\Extbase\Persistence\Repository;
class ArticleRepository extends Repository
{
protected array $defaultOrderings = ['sorting' => QueryInterface::ORDER_ASCENDING];
}
Attention
Without extends Repository the persistence manager cannot register your
repository and all findAll() / findBy*() calls will fail.
Model Classes
Before (TYPO3 11 generated):
class Article extends AbstractEntity
{
/**
* @var string
*/
protected $title;
/**
* @var ObjectStorage<Tag>
*/
protected $tags;
}
After (TYPO3 13 generated):
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Article extends AbstractEntity
{
protected string $title = '';
protected ObjectStorage $tags;
public function __construct()
{
$this->initializeObject();
}
public function initializeObject(): void
{
$this->tags ??= new ObjectStorage();
}
}
Key differences:
- Native PHP 8.3 property type declarations replace
@vardocblocks ObjectStorageis imported viauseinstead of fully qualified- Default values are explicit (
string $title = '') initializeObject()uses??=(null-coalescing assignment)
Controller Classes
Before (TYPO3 11 generated — setter injection):
class ArticleController extends ActionController
{
/**
* @var ArticleRepository
*/
protected $articleRepository;
public function injectArticleRepository(ArticleRepository $articleRepository): void
{
$this->articleRepository = $articleRepository;
}
}
After (TYPO3 13 generated — constructor injection):
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ArticleController extends ActionController
{
public function __construct(
private readonly ArticleRepository $articleRepository,
) {}
public function listAction(): ResponseInterface
{
$articles = $this->articleRepository->findAll();
$this->view->assign('articles', $articles);
return $this->htmlResponse();
}
}
Key differences:
- Constructor injection with
readonlyproperties replaces setter injection ResponseInterfaceis imported viauseinstead of fully qualified- All action methods declare
ResponseInterfaceas return type
TCA Configuration
TYPO3 13 requires a type field in every TCA table that maps to Extbase
models. The Extension Builder generates this automatically.
Required in Configuration/TCA/tx_myext_domain_model_article.php:
'columns' => [
'sys_language_uid' => [
'config' => [
'type' => 'language', // native type since TYPO3 12
],
],
'crdate' => [
'config' => [
'type' => 'datetime', // replaces 'input' with eval=>'datetime'
],
],
],
Module Registration
Before (TYPO3 11 — ext_tables.php):
// ext_tables.php
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
'MyExt',
'web',
'mymodule',
'',
[MyController::class => 'list,show'],
['access' => 'user,group', ...]
);
After (TYPO3 13 — Configuration/Backend/Modules.php):
return [
'web_myextmymodule' => [
'parent' => 'web',
'access' => 'user,group',
'iconIdentifier' => 'my-ext-module',
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'MyExt',
'controllerActions' => [
MyController::class => ['list', 'show'],
],
],
];
Dependency Injection
All TYPO3 13 extensions must have Configuration/Services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\MyExt\:
resource: '../Classes/*'
This enables constructor injection for all classes under Classes/.
Migration Checklist
Use this checklist when migrating a previously generated extension:
PHP / Backend
[ ] All repository classes extend TYPO3\CMS\Extbase\Persistence\Repository
[ ] All model properties have native PHP type declarations
[ ] Controllers use constructor injection (not injectX() methods)
[ ] All action methods return ResponseInterface
[ ] declare(strict_types=1) at top of every PHP file
TCA
[ ] sys_language_uid uses type = 'language'
[ ] Date/time fields use type = 'datetime'
[ ] No deprecated type = 'input' with eval for dates
Module / Routing
[ ] Module registered in Configuration/Backend/Modules.php (not ext_tables.php)
[ ] No TBE_MODULES array manipulation
Dependency Injection
[ ] Configuration/Services.yaml present with autowire: true
[ ] ext_localconf.php does not use makeInstance for services