From craft-workspace-webconsulting-skills
Simplifies and refines TYPO3 extension code (PHP, Fluid, TCA, YAML) for v14 best practices, replacing deprecated patterns with core APIs. Run after implementing a feature or before merging a PR.
How this skill is triggered — by the user, by Claude, or both
Slash command
/craft-workspace-webconsulting-skills:typo3-simplifyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Adapted from Boris Cherny's (Anthropic) code-simplifier agent for TYPO3 contexts.
Adapted from Boris Cherny's (Anthropic) code-simplifier agent for TYPO3 contexts. Target: TYPO3 v14.x only.
Simplify and refine recently changed TYPO3 code. Preserve all functionality. Focus on clarity over cleverness, TYPO3 API usage over custom implementations, and v14 patterns over deprecated approaches.
git diff --name-only HEAD~1 or staged changes)Find custom implementations that duplicate what TYPO3 already provides.
| Custom Pattern | TYPO3 API Replacement |
|---|---|
Manual DB queries ($connection->executeQuery(...)) | QueryBuilder with named parameters |
$GLOBALS['TYPO3_REQUEST'] | Inject ServerRequestInterface via middleware/controller |
new FlashMessage(...) + manual queue | FlashMessageService via DI |
| Manual JSON response construction | JsonResponse from PSR-7 |
GeneralUtility::makeInstance() | Constructor injection via Services.yaml |
$GLOBALS['TSFE']->id | $request->getAttribute('frontend.page.information')?->getId() (canonical on v14; routing page id also works) |
$GLOBALS['BE_USER'] | Inject Context or BackendUserAuthentication |
ObjectManager::get() | Constructor DI |
| Manual file path resolution | PathUtility, Environment::getPublicPath() |
| Custom caching with globals | CacheManager via DI with cache configuration |
BackendUtility::getRecord() for single field | QueryBuilder selecting only needed columns |
| Manual page tree traversal | RootlineUtility or PageRepository |
GeneralUtility::_GP() / _POST() / _GET() | $request->getQueryParams() / getParsedBody() |
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF'] writes | PSR-14 events or ExtensionConfiguration |
| Manual link generation | UriBuilder (backend) or ContentObjectRenderer::typoLink() |
| Deprecated | v14 Replacement |
|---|---|
ext_localconf.php hook arrays | #[AsEventListener] on PSR-14 events |
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'] hooks | PSR-14 events |
ext_tables.php module registration | Configuration/Backend/Modules.php |
XCLASS | PSR-14 events or DI decoration |
TCA eval for required, null | Dedicated TCA keys: required, nullable |
TCA eval for trim | Still valid in v14 — no dedicated TCA replacement; keep trim in eval when you need it |
renderType => 'inputDateTime' | 'type' => 'datetime' |
'type' => 'input', 'eval' => 'int' | 'type' => 'number' |
items with numeric array keys | items with label/value keys |
| Removed / legacy | When | Modern approach |
|---|---|---|
switchableControllerActions | Deprecated in v10.3 (#89463), removed in v12.0 | Separate plugin registrations |
Signal/Slot Dispatcher | Removed in v12 | PSR-14 events |
AbstractPlugin (pi_base) | Made @internal v12.0 (#98281), deprecated v12.4 (#100639), removed v13.0 | Extbase or middleware |
| Topic | Guidance |
|---|---|
$querySettings->setRespectStoragePage(false) | Valid Extbase API — prefer setting query settings in the repository factory or controller, not scattered in repositories, for clarity |
declare(strict_types=1) on every PHP filefinal on classes not designed for inheritancereadonly on immutable propertiesuse imports@)array typehints with typed arrays or DTOs// Before
class MyService
{
private ConnectionPool $connectionPool;
private Context $context;
public function __construct(ConnectionPool $connectionPool, Context $context)
{
$this->connectionPool = $connectionPool;
$this->context = $context;
}
public function getData($id)
{
// ...
}
}
// After
final class MyService
{
public function __construct(
private readonly ConnectionPool $connectionPool,
private readonly Context $context,
) {}
public function getData(int $id): array
{
// ...
}
}
<f:translate> instead of hardcoded strings<f:link.page> / <f:link.typolink> instead of manual <a href><f:image> instead of manual <img> tags{variable -> f:format.raw()} unless absolutely necessary (XSS risk)<f:section> blocks<f:if> to <f:switch> or ternary where cleareritems format with label/value keys'exclude' => true on fields already restricted'type' => 'email', 'type' => 'datetime', 'type' => 'number', 'type' => 'link', 'type' => 'color', 'type' => 'json''required' => true instead of 'eval' => 'required''nullable' => true instead of 'eval' => 'null'eval => trim remains supported — do not remove unless you replace trimming in another layer'default' => '' on string fields (already default)columns definitions auto-created from ctrl on TYPO3 v14: hidden, starttime, endtime, fe_group, sys_language_uid, l10n_parent, l10n_diffsourcehidden for hidden, access for starttime, endtime, fe_group (verify keys in your table’s types showitem)ext_tables.sql when ctrl enables them (hidden, starttime, endtime, fe_group, sys_language_uid, l10n_parent, l10n_source, sorting, deleted, crdate, tstamp) — only if they are auto-managed for that tableshowitem fields from types_defaults: autowire: true, autoconfigure: true, public: falsefactory: blocks when autowiring handles construction; keep factory: for non-trivial construction that autowiring cannot resolve. #[Autoconfigure] tags/configures services but does not replace factory: constructionpublic: true unless needed for GeneralUtility::makeInstance()Configuration/ files where possibleExtensionUtility::configurePlugin() in ext_localconf.php and ExtensionUtility::registerPlugin() (or TCA items) in Configuration/TCA/Overrides/tt_content.php — both are required for a complete registrationaddPageTSConfig — use Configuration/page.tsconfigaddUserTSConfig — use Configuration/user.tsconfigaddTypoScript — use Configuration/TypoScript/setup.typoscript*setMaxResults() when expecting single rowcount() queries instead of fetching all rows to countJOIN or batch loading)TYPO3\CMS\Core\Resource\ProcessedFileRepository not re-processing on every requestfindAll() calls without pagination#[\TYPO3\CMS\Extbase\Attribute\ORM\Lazy] (replace legacy @Lazy annotation)foreach + manual in_array() filtering with QueryBuilder WHERE INcache:flush calls in CLI commandsAfter analysis, report findings grouped by file:
## Classes/Controller/MyController.php
:42 — replace GeneralUtility::makeInstance(MyService::class) → constructor injection
:18 — add return type `: ResponseInterface`
:55 — deprecated: $GLOBALS['TSFE']->id → $request routing attribute
:67 — guard clause: invert condition, return early, reduce nesting
## Resources/Private/Templates/List.html
:12 — hardcoded string "No items found" → f:translate
:34 — manual <a href> → f:link.page
## Configuration/TCA/Overrides/tt_content.php
:8 — legacy items format `['Label', 'value']` → `['label' => 'Label', 'value' => 'value']`
Applied 7 fixes. No behavior changes. Run tests to verify.
When the same codebase must run on TYPO3 v13 and v14 (dual-version extensions), you may temporarily keep transitional patterns:
Configuration/RequestMiddlewares.php (Core-supported pattern)Services.yaml event listener config alongside #[AsEventListener]items arrays alongside label/value formatServices.yaml _defaults (autowire / autoconfigure) plus Core registration attributes (#[AsCommand], #[AsEventListener], …) over ad-hoc factory: blocksThe following simplification opportunities are v14-specific.
| Pattern | Simplification |
|---|---|
$GLOBALS['TSFE'] access | Fatal error in v14 (Breaking #107831) — replace with $request->getAttribute('frontend.page.information') |
Extbase annotations (@validate) | Replace with #[\TYPO3\CMS\Extbase\Attribute\Validate] |
MailMessage->send() | Inject TYPO3\CMS\Core\Mail\MailerInterface and call $this->mailer->send($email) |
FlexFormService usage | Prefer FlexFormTools; FlexFormService remains as a BC alias in v14 but should not be used in new code |
| Bootstrap Modal JS | Frontend: native <dialog> where appropriate. Backend: use @typo3/backend/modal — it wraps native <dialog> in v14 (Breaking #107443); do not use raw Bootstrap modal JS |
TCA ctrl.searchFields (removed in v14) | TYPO3 v14 derives backend search fields automatically; tune inclusion per column with 'searchable' => true/false (supported field types: input, text, email, link, slug, color, datetime (without custom dbType), flex, json, uuid) where supported |
| Custom localization parsers | Superseded by Symfony Translation Component integration (Feature #107436); BC maintained in v14 — prefer the new Symfony Translation API in new code |
GeneralUtility::createVersionNumberedFilename() | Replace with SystemResourceFactory / SystemResourcePublisherInterface (System Resource API, Feature #107537) |
This skill is based on the excellent work by Anthropic.
Original repository: https://github.com/anthropics/claude-plugins-official/tree/main/plugins/code-simplifier
Special thanks to Anthropic for their generous open-source contributions, which helped shape this skill collection. Adapted by webconsulting.at for this skill collection
npx claudepluginhub dirnbauer/webconsulting-skillsPlans and executes batch TYPO3 migrations and large-scale refactors across hooks, events, TCA, Fluid, namespaces, and PHP upgrades. Useful for codemod-style mass updates or v14 modernization.
Analyzes, plans, and executes TYPO3 v13 to v14 upgrades in Composer-based projects: checks extension compatibility, deprecations, Rector/Fractor migrations, and system requirements.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.