typo3/cms-backend
The TYPO3 CMS backend package supplies the core administrative interface and framework: backend modules, user and group management, authentication, routing, layouts, and UI components for editors and administrators. It is a foundational system extension for extending TYPO3 backend functionality.
First, calibrate the mental model: typo3/cms-backend is not a Laravel package. It is one system extension inside the TYPO3 Core monorepo (typo3/sysext/backend/) that happens to be published as a split Composer package. There is no service provider contract, no facades, no Eloquent. Dropping it into a composer.json of a Laravel app gets you files on disk and nothing else — it needs TYPO3\CMS\Core\Core\Bootstrap, a compiled Symfony DI container and the index.php entry point (SystemEnvironmentBuilder::run() → Bootstrap::init() → Core\Http\Application). Treat it as a place to learn and borrow architecture from, or as a dependency you will only encounter inside an actual TYPO3 project.
To actually run it:
composer create-project typo3/cms-base-distribution my-site
# or, inside an existing TYPO3 install
composer require typo3/cms-backend
Where to look first, in this order:
typo3/sysext/backend/Configuration/Backend/Modules.php — the module registry. This is the closest thing to routes/web.php + a menu definition in one file.typo3/sysext/backend/Configuration/Backend/Routes.php and AjaxRoutes.php — backend routing, the analogue of web.php and api.php.typo3/sysext/backend/Configuration/RequestMiddlewares.php — the PSR-15 stack for the backend request type.typo3/sysext/backend/Classes/ — controllers, ModuleTemplateFactory, DocHeader buttons, toolbar items, backend user auth.typo3/sysext/backend/Classes/ServiceProvider.php — the failsafe container wiring (see Gotchas).First use case — a backend module. This is the "hello world" equivalent and mirrors Route::get(...)->middleware('auth') plus a Blade view:
// Configuration/Backend/Modules.php
return [
'web_myext' => [
'parent' => 'web',
'position' => ['before' => ['web_list']],
'access' => 'user',
'path' => '/module/web/myext',
'controller' => \Vendor\MyExt\Controller\OverviewController::class,
'labels' => 'myext.modules.overview',
'icon' => 'EXT:myext/Resources/Public/Icons/module.svg',
],
];
…paired with a route in Configuration/Backend/Routes.php mapping the path to OverviewController::class . '::mainAction', a Fluid template under Resources/Private/Templates/, and a label in Resources/Private/Language/locallang_modules.xlf. Register the extension's own Configuration/Backend/Modules.php in ext_localconf.php? No — it is picked up automatically by the module registry.
Backend routing. Routes.php and AjaxRoutes.php both return path => ['target' => 'Fqcn::method', 'access' => 'user']. Routes.php entries render full module pages; AjaxRoutes.php entries are JSON endpoints for widgets, tree loaders, autocomplete — the direct analogue of routes/api.php. Controllers receive the PSR-7 ServerRequestInterface and a Route object, or you resolve services through constructor injection.
Middleware. Configuration/RequestMiddlewares.php declares per-request-type stacks:
return [
'backend' => [
'myext/tenant-scope' => [
'target' => \Vendor\MyExt\Middleware\TenantScope::class,
'before' => ['typo3/backend/authentication'],
'after' => ['typo3/backend/locale'],
],
],
];
A MiddlewareInterface implementation is process(ServerRequestInterface $request, RequestHandlerInterface $handler) — same shape as Laravel's $next($request) closure, but handler-based. Ordering is resolved by Http\MiddlewareStackResolver from the before/after keys; there is no numeric priority.
Events over hooks. Old TYPO3 "hooks" ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']) are legacy. New extension points are PSR-14 events dispatched through Core\EventDispatcher. Register with the attribute on an autoconfigured listener class — this is exactly a Laravel listener:
#[AsEventListener]
public function __invoke(ModifyButtonBarEvent $event): void { /* … */ }
Alternatively tag the service event.listener in Services.yaml. When replacing a hook, ship both the event class and a Feature-*.rst changelog entry.
Dependency injection. Configuration/Services.yaml uses autowire: true, autoconfigure: true, public: false plus a resource: '../Classes/*' sweep, so constructor injection "just works" like Laravel's auto-resolution. Explicit entries are only needed for tags, factories and non-shared services. Extension points are consumed with !tagged_iterator (e.g. backend.toolbar for toolbar items) — the equivalent of Laravel's tagged() + TaggedIterator.
TCA instead of migrations. Records are configured in Configuration/TCA/<table>.php for own tables and Configuration/TCA/Overrides/<table>.php for foreign tables (pages, tt_content, be_users). New code should not touch $GLOBALS['TCA']; use Core\Schema\TcaSchemaFactory, which exposes typed TcaSchema/field/capability/relation objects — think typed casts/DTOs rather than array access. Core\DataHandling\DataHandler is still the single write path (versioning, workspaces, localization), so it is your "form request + transaction" layer.
Labels. Put English source strings in Resources/Private/Language/*.xlf and reference them with the translation-domain syntax introduced in v14.0: backend.modules.list, core.bookmarks:title, <f:translate key="myext.modules.overview" />. On 13.4 backports, use the legacy LLL:EXT:backend/Resources/Private/Language/locallang.xlf:identifier form instead.
Frontend assets. TypeScript/Sass sources live in Build/Sources/TypeScript/backend/ and Build/Sources/Sass/; compile via ./Build/Scripts/runTests.sh -s build. The compiled Resources/Public/{JavaScript,Css} is build output — never hand-edit it.
Testing. Everything is wrapped: CI=true ./Build/Scripts/runTests.sh -s unit, -s functional -d sqlite (default and fastest), -- --filter SomeTest. Unit tests extend TYPO3\TestingFramework\Core\Unit\UnitTestCase; functional tests extend ...\Core\Functional\FunctionalTestCase and get a real provisioned instance under typo3temp/var/tests/, resolving services with $this->get(SomeClass::class). Fixture extensions live in Tests/Functional/Fixtures/Extensions/, DB fixtures are CSV loaded with importCSVDataSet().
composer.json maps PSR-4 namespaces and the code assumes the TYPO3 bootstrap, container and Environment. There is no Laravel service provider, no config/*.php publish step. Learn from the patterns; don't composer require it into an app.backend is one of five extensions (with core, install, fluid, dashboard) that provide Classes/ServiceProvider.php for the minimal container used by the install tool and broken-configuration states. Any class reachable from that path — including Fluid ViewHelpers used in install templates — that gains a required constructor argument must be wired manually there. Autowiring does not apply on this path.Resources/Public/JavaScript and Resources/Public/Css are generated. Change Build/Sources/ and rebuild. Also mind the naming shift: TypeScript folders use dashes (rte-ckeditor), sysexts use underscores (rte_ckeditor).throw new SiteNotFoundException('…', 1521668882);. Never copy an existing code. ./Build/Scripts/runTests.sh -s listExceptionCodes reports duplicates and missing codes.@deprecated since v<current>, will be removed in v<next>) and a trigger_error(..., E_USER_DEPRECATED) in the deprecated path, plus an RST changelog and extension-scanner entry. Never break API on a release branch.before/after are soft references resolved by MiddlewareStackResolver; ambiguous or cyclic constraints throw at container compile time — often on a cold cache, not on your machine.EXT:. importCSVDataSet() and pathsToProvideInTestInstance are plain file lookups. Use __DIR__-relative paths, not EXT:myext/....phpunit, phpstan, php-cs-fixer, npm or grunt directly — runTests.sh supplies the PHP version (-p), the DB service (-d sqlite|mysql|mariadb|postgres), chunking (-c 3/13) and bootstrap. When a suite fails in a way the code can't explain, -u refreshes the typo3/core-testing-* images first; -x forwards xdebug to port 9003.Build/phpstan/phpstan-baseline.neon; regenerate it only after a PHPStan upgrade (-s phpstanGenerateBaseline).-s checkIntegrityXliff; -s normalizeXliff fixes formatting. Edit only the English source files — translations are managed on Crowdin and are not in this repo.$GLOBALS['TCA'] in new code. TcaSchemaFactory is the supported API; the global is kept for backwards compatibility only.Modules.php → navigation + route groups in one declarative file; AjaxRoutes.php → routes/api.php; #[AsEventListener] → EventServiceProvider discovery; !tagged_iterator → container tags; TcaSchemaFactory → typed resource/cast layer over $GLOBALS-style arrays. These are the parts that translate cleanly back into day-to-day Laravel work; the rest is TYPO3-specific runtime you can't port.How can I help you explore Laravel packages today?