Roll out the same change across many TYPO3 extensions at once: plans and executes batch migrations and large-scale refactors over every extension in a monorepo or packages/ directory. Covers hooks to PSR-14 events, TCA, dependency injection, Fluid, namespaces, ext_localconf, Content Blocks, localization and PHP upgrades. Use when the user says roll out a change to every extension, inventory which extensions still use a removed API such as HashService and fix them together, apply the same fix across the monorepo, mass or codemod-style refactor, or sequence a large refactor so it stays reviewable. Requires a TYPO3 codebase and more than one extension; a single extension is typo3-rector.
Roll out the same change across many TYPO3 extensions at once: plans and executes batch migrations and large-scale refactors over every extension in a monorepo or packages/ directory. Covers hooks to PSR-14 events, TCA, dependency injection, Fluid, namespaces, ext_localconf, Content Blocks, localization and PHP upgrades. Use when the user says roll out a change to every extension, inventory which extensions still use a removed API such as HashService and fix them together, apply the same fix across the monorepo, mass or codemod-style refactor, or sequence a large refactor so it stays reviewable. Requires a TYPO3 codebase and more than one extension; a single extension is typo3-rector.
Adapted from Boris Cherny's (Anthropic) Claude Code /batch skill for TYPO3 contexts.
Target: TYPO3 v14.x only.
Orchestrate large-scale, parallelizable changes across a TYPO3 codebase. Decompose work
into 5–30 independent units, present a plan, then execute each unit with verification.
Process
Research: Scan codebase to find all affected files
Decompose: Split work into independent, non-conflicting units
Plan: Present numbered plan to user for approval
Execute: Apply each unit, run verification after each
Report: Summarize changes, list any failures
Execution Rules
Each unit must be independently verifiable
Never modify the same file in two different units
Run composer normalize / php -l / PHPStan after PHP changes
Run tests if available (vendor/bin/phpunit)
Commit each unit separately with descriptive message
Stop and report if a unit breaks tests
Common TYPO3 Batch Operations
1. Hook → PSR-14 Event Migration
Research: Find all entries in $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'] and
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF'].
Decompose: One unit per hook class.
Per unit:
1. Identify the hook interface/method
2. Find the corresponding PSR-14 event (see mapping below)
3. Create new event listener class with #[AsEventListener]
4. Move logic from hook method to __invoke()
5. Remove hook registration from ext_localconf.php
6. If **not** using `#[AsEventListener]`: register the listener in `Services.yaml`. With the attribute and `autoconfigure: true` (default), no extra `tags:` entry is required.
7. Run php -l on new file
DataHandler (t3lib/class.t3lib_tcemain.php): TYPO3 Core does not expose generic PSR-14 events named like BeforeRecordOperationEvent / AfterRecordOperationEvent under TYPO3\CMS\Core\DataHandling\Event. For datamap/cmdmap reactions, use SC_OPTIONS hook classes (see typo3-datahandler §9). Do not “migrate” those hooks to non-existent events.
Hook / legacy API
Typical direction
processDatamap_afterDatabaseOperations / related SC_OPTIONS
Keep processDatamapClass hook methods (no 1:1 Core event)
processDatamap_preProcessFieldArray
Keep hook or narrow Core events for your table/field if documented
processCmdmap_postProcess
Keep processCmdmapClass hook methods
drawHeaderHook
ModifyPageLayoutContentEvent (Breaking #96526)
drawFooterHook
ModifyPageLayoutContentEvent (Breaking #96526)
checkFlexFormValue
Still often a hook; AfterFlexFormDataStructureParsedEvent is for DS parsing, not a drop-in for validation — confirm in Core for your case
No 1:1 PSR-14 replacement; prefer PSR-15 middleware for render-pipeline manipulation
tslib_fe->contentPostProc (cached)
AfterCacheableContentIsGeneratedEvent
tslib_fe->contentPostProc (output)
No PSR-14 event — use a PSR-15 middleware (Deprecation #91012); AfterCachedPageIsPersistedEvent replaces insertPageIncache, not this hook
tslib_fe->contentPostProc (all)
AfterCacheableContentIsGeneratedEvent (Breaking #97862; also covers usePageCache)
BackendUtility->getPagesTSconfig
ModifyLoadedPageTsConfigEvent
generatePageTSconfig
ModifyLoadedPageTsConfigEvent
Not 1:1 replacements (verify before batch-rewriting):
drawHeaderHook / drawFooterHook — Page module header/footer integration points. Replaced by ModifyPageLayoutContentEvent (Breaking #96526), which lets listeners add or change header and footer content; ModifyButtonBarEvent covers the button bar only and is not the migration target.
tslib_fe->contentPostProc — Sub-hooks (all, cached, output) run at different FE pipeline stages. Map cached and all (plus usePageCache) → AfterCacheableContentIsGeneratedEvent, and insertPageIncache → AfterCachedPageIsPersistedEvent (Breaking #97862); output has no PSR-14 event — use a PSR-15 middleware instead.
Research: Scan all Configuration/TCA/ and Configuration/TCA/Overrides/ files.
Decompose: One unit per TCA file.
Per unit:
1. Replace 'eval' => 'required' → 'required' => true
2. Replace 'eval' => 'trim' → keep where trimming is desired (trim is NOT applied by default)
3. Replace 'eval' => 'null' → 'nullable' => true
4. Replace 'eval' => 'int' → 'type' => 'number'
5. Replace 'renderType' => 'inputDateTime' → 'type' => 'datetime'
6. Replace 'renderType' => 'inputLink' → 'type' => 'link'
7. Replace 'renderType' => 'colorPicker' → 'type' => 'color'
8. Replace 'type' => 'input', 'eval' => 'email' → 'type' => 'email'
9. Convert items arrays: [0] => label, [1] => value → ['label' => ..., 'value' => ...]
10. Remove boilerplate columns auto-created from `ctrl` on TYPO3 v14: hidden, starttime, endtime, fe_group, language fields
11. Use palettes for enablecolumns: visibility → hidden, access → starttime, endtime
12. Remove convention fields from ext_tables.sql (auto-added to database)
13. Run php -l to verify syntax
3. GeneralUtility::makeInstance → DI
Research: Find all GeneralUtility::makeInstance() calls in Classes/.
Decompose: One unit per class file.
Per unit:
1. Identify all makeInstance calls in the class
2. For each: add constructor parameter with type
3. Replace makeInstance calls with $this->propertyName
4. Add/update Services.yaml if needed
5. Use readonly promoted properties
6. Verify with php -l
Research: Scan ext_localconf.php and ext_tables.php for movable code.
Decompose: One unit per concern (TSconfig, TypoScript, icons, modules, plugins).
Per unit:
Page TSconfig:
1. Extract addPageTSConfig() calls
2. Create Configuration/page.tsconfig
3. Remove from ext_localconf.php
User TSconfig:
1. Extract addUserTSConfig() calls
2. Create Configuration/user.tsconfig
3. Remove from ext_localconf.php
Icons:
1. Extract icon registry calls
2. Move to Configuration/Icons.php (v14)
3. Remove from ext_localconf.php
Backend modules:
1. Extract registerModule() calls
2. Move to Configuration/Backend/Modules.php
3. Remove from ext_tables.php
7. Test Infrastructure Setup
Research: Check if Tests/ directory exists, find testable classes.
Decompose: One unit per test type.
Per unit:
Unit tests:
1. Create Tests/Unit/ structure
2. Generate test class per service/utility class
3. Add phpunit.xml.dist configuration
Functional tests:
1. Create Tests/Functional/ structure
2. Add fixture files
3. Generate test for repository/DataHandler usage
CI pipeline:
1. Create .github/workflows/ci.yml
2. Configure PHP matrix (8.2, 8.3, 8.4, 8.5)
3. Configure CI matrix (TYPO3 v14, PHP 8.2+)
8. PHP 8.4 Migration
Research: Scan Classes/ for PHP 8.4 migration candidates.
Decompose: One unit per class file.
Per unit:
1. Add property hooks where getter/setter pattern exists
2. Use asymmetric visibility (public private(set)) on DTOs
3. Replace array_search + if with array_find() **only after checking semantics** — `array_search()` returns a **key** for a given value; `array_find()` takes a **callback** and returns the first matching **value** (or null). Not drop-in replacements.
4. Replace array_filter + reset with array_find() where a callback-based “first match” is what you need
5. Replace `foreach` + conditional checks with `array_any()` / `array_all()` for value-based logic. These do **not** replace `array_key_exists()` (key-based checks).
6. Add #[\Deprecated] attribute to legacy methods
7. Run php -l to verify syntax
9. Content Blocks Migration
Research: Scan TCA, ext_tables.sql, and Fluid templates for classic content elements.
Decompose: One unit per content element / record type.
Per unit:
1. Create ContentBlocks/ContentElements/<name>/config.yaml
2. Map TCA columns to YAML fields
3. Move Fluid template to `ContentBlocks/ContentElements/<name>/templates/` (not `Source/`)
4. Align field definitions in `config.yaml` with TCA columns (no separate `EditorInterface.yaml` in Content Blocks)
5. Remove old TCA override file
6. Remove SQL from ext_tables.sql (columns become automatic)
7. Test rendering in frontend
10. Localization / XLIFF Batch
Research: Find all hardcoded strings in Fluid, PHP flash messages, TCA labels.
Decompose: One unit per language file scope (frontend, backend, TCA).
Per unit:
1. Extract strings from templates/PHP
2. Generate XLIFF keys following convention
3. Add entries to Resources/Private/Language/locallang.xlf
4. Replace hardcoded strings with LLL: references
5. Create de.locallang.xlf with German translations (if applicable)