--- name: typo3-update description: Comprehensive guide for writing TYPO3 code compatible with both v13 and v14, with preference for v14. Covers version constraints, compatible patterns, and migration strategies. version: 2.0.0 typo3_compatibility: "13.0 - 14.x" triggers: - update - upgrade - v13 - v14 - migration - lts - compatibility --- # TYPO3 Dual-Version Development: v13 & v14 > **Strategy:** Write code that works on both TYPO3 v13 and v14, with v14 as the preferred target. > All patterns in this skill are designed for dual-version compatibility. ## 1. Version Strategy ### Target: TYPO3 v13 and v14 | Version | Status | PHP | Support Until | |---------|--------|-----|---------------| | v12.4 LTS | Maintenance | 8.1-8.3 | April 2026 | | **v13.4 LTS** | **Active LTS** | 8.2-8.4 | ~2028 | | **v14.x** | **Latest** | 8.2-8.4 | ~2029 | **Best Practice:** Target both v13 and v14 for maximum compatibility. New projects should prefer v14. ### Version Constraints ```php 'My Extension', 'version' => '2.0.0', 'state' => 'stable', 'constraints' => [ 'depends' => [ 'typo3' => '13.0.0-14.99.99', 'php' => '8.2.0-8.4.99', ], 'conflicts' => [], 'suggests' => [], ], ]; ``` ```json // composer.json - Dual version support { "name": "vendor/my-extension", "type": "typo3-cms-extension", "require": { "php": "^8.2", "typo3/cms-core": "^13.0 || ^14.0" }, "extra": { "typo3/cms": { "extension-key": "my_extension" } } } ``` ## 2. PHP Requirements ### Minimum PHP Version: 8.2 Both v13 and v14 require PHP 8.2+. Use modern PHP features: ```php doSomething( name: 'value', options: ['key' => 'value'], ); // ✅ Match expressions (PHP 8.0+) $type = match ($input) { 'a' => 'Type A', 'b' => 'Type B', default => 'Unknown', }; // ✅ Enums (PHP 8.1+) enum Status: string { case Draft = 'draft'; case Published = 'published'; } ``` ## 3. Controller Patterns (v13/v14 Compatible) ### Extbase Action Controller ```php itemRepository->findAll(); $this->view->assign('items', $items); return $this->htmlResponse(); } public function showAction(int $item): ResponseInterface { $item = $this->itemRepository->findByUid($item); $this->view->assign('item', $item); return $this->htmlResponse(); } // ✅ JSON response public function apiAction(): ResponseInterface { $data = ['success' => true, 'items' => []]; return $this->jsonResponse(json_encode($data)); } // ✅ Redirect public function createAction(): ResponseInterface { // Process creation... $this->addFlashMessage('Item created'); return $this->redirect('list'); } } ``` ### Backend Module Controller ```php moduleTemplateFactory->create($request); $moduleTemplate->assign('items', []); return $moduleTemplate->renderResponse('Backend/Index'); } } ``` ## 4. View & Templating (v13/v14 Compatible) ### ViewFactory (Preferred Pattern) ```php viewFactory->create($viewFactoryData); $view->assignMultiple($data); return $view->render('Notification'); } } ``` ### Fluid Template Best Practices ```html

{item.title}

{item.bodytext}

No items found.

View Details
``` ## 5. Event System (v13/v14 Compatible) ### PSR-14 Event Listeners PSR-14 events are the standard in both v13 and v14. Always prefer events over hooks. ```php getPageId() === 123) { $event->setCacheLifetime(300); // 5 minutes } } } ``` ### Common Events (v13/v14) | Event | Purpose | |-------|---------| | `BeforeRecordOperationEvent` | Before DataHandler operations | | `AfterRecordOperationEvent` | After DataHandler operations | | `ModifyPageLinkConfigurationEvent` | Modify link building | | `ModifyCacheLifetimeForPageEvent` | Adjust page cache | | `BeforeStdWrapFunctionsInitializedEvent` | Modify stdWrap | ### Services.yaml Registration ```yaml # Configuration/Services.yaml services: _defaults: autowire: true autoconfigure: true public: false Vendor\Extension\: resource: '../Classes/*' exclude: - '../Classes/Domain/Model/*' # Event listener (alternative to #[AsEventListener] attribute) Vendor\Extension\EventListener\MyListener: tags: - name: event.listener identifier: 'vendor-extension/my-listener' ``` ## 6. Backend Module Registration (v13/v14) ### Configuration/Backend/Modules.php ```php [ 'parent' => 'web', 'position' => ['after' => 'web_info'], 'access' => 'user,group', 'iconIdentifier' => 'myextension-module', 'path' => '/module/web/myextension', 'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf', 'extensionName' => 'MyExtension', 'controllerActions' => [ \Vendor\MyExtension\Controller\BackendController::class => [ 'index', 'list', 'show', ], ], ], ]; ``` ### Icon Registration ```php [ 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, 'source' => 'EXT:my_extension/Resources/Public/Icons/module.svg', ], ]; ``` ## 7. TCA Configuration (v13/v14 Compatible) ### Static TCA Only In v14, runtime TCA modifications are forbidden. Always use static files: ```php [ 'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item', 'label' => 'title', 'tstamp' => 'tstamp', 'crdate' => 'crdate', 'delete' => 'deleted', 'enablecolumns' => [ 'disabled' => 'hidden', 'starttime' => 'starttime', 'endtime' => 'endtime', ], 'searchFields' => 'title,description', 'iconIdentifier' => 'myextension-item', ], 'types' => [ '1' => [ 'showitem' => ' --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general, title, description, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime, ', ], ], 'columns' => [ 'title' => [ 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.title', 'config' => [ 'type' => 'input', 'size' => 50, 'max' => 255, 'required' => true, ], ], 'description' => [ 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_item.description', 'config' => [ 'type' => 'text', 'cols' => 40, 'rows' => 5, 'enableRichtext' => true, ], ], ], ]; ``` ### TCA Overrides ```php 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:ctype.title', 'value' => 'myextension_element', 'icon' => 'content-text', 'group' => 'default', ] ); $GLOBALS['TCA']['tt_content']['types']['myextension_element'] = [ 'showitem' => ' --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general, --palette--;;general, header, bodytext, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, --palette--;;hidden, ', ]; ``` ## 8. Database Operations (v13/v14 Compatible) ### QueryBuilder ```php connectionPool->getQueryBuilderForTable('tx_myext_items'); return $queryBuilder ->select('*') ->from('tx_myext_items') ->where( $queryBuilder->expr()->eq( 'status', $queryBuilder->createNamedParameter($status) ) ) ->orderBy('title', 'ASC') ->executeQuery() ->fetchAllAssociative(); } public function countByPid(int $pid): int { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_myext_items'); return (int)$queryBuilder ->count('uid') ->from('tx_myext_items') ->where( $queryBuilder->expr()->eq( 'pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT) ) ) ->executeQuery() ->fetchOne(); } } ``` ### Extbase Repository ```php QueryInterface::ORDER_ASCENDING, ]; public function findPublished(): array { $query = $this->createQuery(); $query->matching( $query->logicalAnd( $query->equals('hidden', false), $query->lessThanOrEqual('starttime', time()), $query->logicalOr( $query->equals('endtime', 0), $query->greaterThan('endtime', time()) ) ) ); return $query->execute()->toArray(); } } ``` ## 9. CLI Commands (v13/v14 Compatible) ```php addArgument('type', InputArgument::REQUIRED, 'The type to process') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force processing'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); // Initialize backend for DataHandler operations Bootstrap::initializeBackendAuthentication(); $type = $input->getArgument('type'); $force = $input->getOption('force'); $io->title('Processing: ' . $type); // Your logic here... $io->success('Processing completed successfully'); return Command::SUCCESS; } } ``` ### Command Registration ```yaml # Configuration/Services.yaml services: Vendor\Extension\Command\ProcessCommand: tags: - name: console.command ``` ## 10. Testing for Dual-Version Compatibility ### PHPUnit Setup ```xml Tests/Unit Tests/Functional ``` ### Test Both Versions in CI ```yaml # .github/workflows/ci.yaml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: typo3: ['^13.0', '^14.0'] php: ['8.2', '8.3'] steps: - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: intl, pdo_mysql - name: Install dependencies run: | composer require typo3/cms-core:${{ matrix.typo3 }} --no-update composer install --prefer-dist --no-progress - name: Run tests run: vendor/bin/phpunit ``` ## 11. Upgrade Process ### From v12 to v13/v14 ```bash # 1. Create backup ddev snapshot --name=before-upgrade # 2. Update composer constraints ddev composer require "typo3/cms-core:^13.0 || ^14.0" --no-update ddev composer update "typo3/*" --with-all-dependencies # 3. Run upgrade wizards ddev typo3 upgrade:list ddev typo3 upgrade:run # 4. Clear caches ddev typo3 cache:flush # 5. Update database schema ddev typo3 database:updateschema # 6. Test thoroughly ``` ## 12. Resources - **v13 Documentation**: https://docs.typo3.org/m/typo3/reference-coreapi/13.4/en-us/ - **v14 Documentation**: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ - **v13 Changelog**: https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog-13/Index.html - **v14 Changelog**: https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog-14/Index.html - **TYPO3 Rector**: https://github.com/sabbelasichon/typo3-rector --- ## Credits & Attribution This skill is based on the excellent TYPO3 best practices and methodology developed by **[Netresearch DTT GmbH](https://www.netresearch.de/)**. We are deeply grateful for their outstanding contributions to the TYPO3 community and their commitment to sharing knowledge. Netresearch has been a leading force in TYPO3 development, and their expertise has been invaluable in shaping these guidelines. Thank you, Netresearch, for your exceptional work! **Copyright (c) Netresearch DTT GmbH** - Methodology and best practices Adapted by webconsulting.at for this skill collection