Guidance for coding agents working in the TYPO3 CMS Core monorepo. Personal or machine-specific preferences do not belong here — put those in an untracked CLAUDE.local.md (already gitignored).
php artisan boost:add-skill typo3/cms-frontend
Save this content to: AGENTS.md
# Repository Guidelines
Guidance for coding agents working in the TYPO3 CMS Core monorepo. Personal or
machine-specific preferences do not belong here — put those in an untracked
`CLAUDE.local.md` (already gitignored).
## Context
- Main code locations: `typo3/sysext/`, `Build/Sources/`.
- Documentation: `typo3/sysext/core/Documentation/`, <https://docs.typo3.org>.
- Issue tracker: <https://forge.typo3.org> (Redmine). Query it through the JSON API
with `curl -H "Accept: application/json"` and the **default curl user agent** —
browser-like user agents hit the Anubis bot check. Useful endpoints:
`issues/<id>.json` (add `?include=journals` for comments),
`search.json?q=<query>`, `issues.json?project_id=27` (27 = TYPO3 Core).
- Code review: <https://review.typo3.org> (Gerrit), open changes at
<https://review.typo3.org/q/project:Packages/TYPO3.CMS>.
- Contribution walkthrough for humans: `CONTRIBUTING.md` and
<https://docs.typo3.org/core-contribution>.
## Working mode
- When a bug is reported or reproduced, **do not start by fixing it**. First write a
test that reproduces it, then fix and prove it with that test passing. The only
exception is a purely mechanical fix with no testable behaviour, such as a typo
in a label, comment or documentation.
- Only add code comments when they add meaning; otherwise leave them out.
- Follow the TYPO3 Coding Guidelines (CGL), and run
`./Build/Scripts/runTests.sh -s cglGit` after committing.
## Project structure & modules
- `typo3/sysext/<ext>/` — the system extensions, each a Composer package
(`typo3/cms-<ext>`) with its own `composer.json`, a PSR-4 namespace under
`TYPO3\CMS\...` mapped in the root `composer.json`, and the same internal shape:
`Classes/`, `Configuration/`, `Resources/{Private,Public}/`,
`Tests/{Unit,Functional}/`, `ext_localconf.php`, `ext_tables.sql`, `Documentation/`.
- `Build/` — all tooling: `Scripts/` (test dispatcher + integrity checks), `phpunit/`,
`phpstan/`, `php-cs-fixer/`, `Sources/` (frontend sources), `tests/` (Playwright
specs and fixture extensions), `Gruntfile.js`, `package.json`.
- `index.php` — the single web entry point: `SystemEnvironmentBuilder::run()` →
`Bootstrap::init()` → `Core\Http\Application`, falling back to
`Install\Http\Application` when the full container cannot be built.
- `typo3conf/`, `typo3temp/`, `vendor/` — local/generated, not versioned.
Frontend sources live in `Build/Sources/TypeScript/<ext-key>/` and
`Build/Sources/Sass/`; the build compiles them into
`typo3/sysext/<ext>/Resources/Public/{JavaScript,Css}/`. **Never edit files under
`Resources/Public/JavaScript` or `Resources/Public/Css` — they are build output.**
Mind the naming shift: TypeScript folders use dashes (`rte-ckeditor`), sysexts use
underscores (`rte_ckeditor`).
## Commands
Everything runs through one container-based dispatcher,
`./Build/Scripts/runTests.sh` (podman by default, `-b docker` to switch). Do not
invoke `phpunit`, `phpstan`, `php-cs-fixer`, `npm` or `grunt` directly — the wrapper
supplies the PHP version, database service and bootstrap. `-h` is the authoritative,
always-current list of suites and options; the examples below are a curated subset.
```bash
# PHP tests (-p selects the PHP version; -h shows which ones this branch accepts)
CI=true ./Build/Scripts/runTests.sh -s unit
CI=true ./Build/Scripts/runTests.sh -s unit typo3/sysext/core/Tests/Unit/RegistryTest.php
CI=true ./Build/Scripts/runTests.sh -s unit -- --filter someTestMethodName
CI=true ./Build/Scripts/runTests.sh -s unitRandom # random order, exposes test isolation bugs
CI=true ./Build/Scripts/runTests.sh -s functional -d sqlite typo3/sysext/core/Tests/Functional/Database
CI=true ./Build/Scripts/runTests.sh -s functional -d postgres -i 17 # also: mariadb, mysql
CI=true ./Build/Scripts/runTests.sh -s functional -c 3/13 # run chunk 3 of 13
# static analysis / style
./Build/Scripts/runTests.sh -s cgl # fix; add -n for dry-run
./Build/Scripts/runTests.sh -s cglGit # CGL of the last commit only
./Build/Scripts/runTests.sh -s phpstan # custom rules, baseline in Build/phpstan/
./Build/Scripts/runTests.sh -s checkIntegrityPhp # registered PHP integrity rules
./Build/Scripts/runTests.sh -s listExceptionCodes # also reports duplicate/missing exception codes
# frontend
CI=true ./Build/Scripts/runTests.sh -s build # TypeScript + Sass + assets
./Build/Scripts/runTests.sh -s unitJavascript # web-test-runner
./Build/Scripts/runTests.sh -s lintTypescript # add -- --fix
./Build/Scripts/runTests.sh -s lintScss
./Build/Scripts/runTests.sh -s npm -- ci
# end-to-end (Playwright)
./Build/Scripts/runTests.sh -s e2e
./Build/Scripts/runTests.sh -s e2e Build/tests/playwright/e2e/form-engine/elements-rte-source-editing.spec.ts
./Build/Scripts/runTests.sh -s e2e -- --grep login
./Build/Scripts/runTests.sh -s e2e-install -d sqlite
./Build/Scripts/runTests.sh -s e2e-prepare # boot an instance for manual poking
./Build/Scripts/runTests.sh -s e2e-browser # same specs with the GUI on http://127.0.0.1:43837
# docs / integrity gates that CI enforces (more check*/lint* suites via -h)
./Build/Scripts/runTests.sh -s checkRst
./Build/Scripts/runTests.sh -s watchRst core interactive # live RST preview / changelog wizard
./Build/Scripts/runTests.sh -s checkExtensionScannerRst
./Build/Scripts/runTests.sh -s lintServicesYaml
./Build/Scripts/runTests.sh -s lintYaml # all YAML except Services.yaml
./Build/Scripts/runTests.sh -s checkIntegrityXliff # add -s normalizeXliff to fix .xlf formatting
# composer inside the container
./Build/Scripts/runTests.sh -s composer -- install
```
Functional tests default to SQLite, which is by far the fastest feedback loop; only
switch DBMS when the change touches SQL/Doctrine specifics.
Two options worth knowing: `-x` forwards xdebug to a listening IDE (port 9003, change
with `-y`), and `-u` updates the `typo3/core-testing-*` images — the first thing to try
when a suite fails in ways the code cannot explain.
## Architecture: how the pieces connect
These five mechanisms explain most of the codebase.
**1. Packages + bootstrap.** `Core\Package\PackageManager` discovers active
extensions; `Core\Core\Bootstrap` builds a Symfony DI container that is compiled and
cached. Extensions contribute via `Configuration/Services.yaml` (+ optional
`Services.php` for compiler passes). A second, minimal *failsafe* container exists for
the install tool / broken configuration, assembled from the handful of
`Classes/ServiceProvider.php` files (core, backend, install, fluid, dashboard) — code
that must work in the installer needs manual wiring there, autowiring does not apply.
**2. Dependency injection.** `Services.yaml` uses
`autowire: true, autoconfigure: true, public: false` with a `resource: '../Classes/*'`
sweep, so classes are autowired by default; only entries needing tags, factories or
non-shared behaviour are listed explicitly. Extension points are wired with *tagged
services* (e.g. `mfa.provider`, `softreference.parser`, `fal.file_renderer`) consumed
via `!tagged_iterator`. Prefer constructor injection —
`GeneralUtility::makeInstance()` is legacy and is actively being reduced.
Fluid ViewHelpers take part in DI as well: a compiler pass in
`fluid/Configuration/Services.php` makes every `ViewHelperInterface` public and
non-shared, so `ViewHelperResolver` resolves them through the container. Inject only
stateless singletons (never `ContentObjectRenderer`, Extbase's `UriBuilder`, `Context`
or other request-scoped objects), never from `static` methods, and remember the
failsafe container: a ViewHelper used in install-tool templates that gains required
constructor arguments must be wired by hand in `install/Classes/ServiceProvider.php`.
**3. PSR-15 request pipeline.** Each request type (`frontend`, `backend`) is a
middleware stack declared per extension in `Configuration/RequestMiddlewares.php`,
with `before`/`after` ordering keys resolved by `Http\MiddlewareStackResolver` and run
by `Http\MiddlewareDispatcher`. Backend routes come from
`Configuration/Backend/{Routes,AjaxRoutes,Modules}.php`; frontend routing is
site-configuration driven (`Core\Routing`, `Core\Site`).
**4. PSR-14 events.** Hooks are legacy; new extension points are events dispatched
through `Core\EventDispatcher`. Register listeners with the `#[AsEventListener]`
attribute (`Core\Attribute\AsEventListener`) or an `event.listener` service tag. When
replacing a hook, add both the event class and a `Feature-*.rst` changelog entry.
**5. TCA and the schema layer.** Table/field configuration lives in
`Configuration/TCA/<table>.php` (own tables) and
`Configuration/TCA/Overrides/<table>.php` (additions to foreign tables). Do not read
`$GLOBALS['TCA']` in new code: `Core\Schema\TcaSchemaFactory` turns TCA into typed
`TcaSchema` / field / capability / relation objects, and that API is what new code
should consume. `Core\DataHandling\DataHandler` remains the central write path for
records (versioning, workspaces, localization).
Extbase (`typo3/sysext/extbase`) and Fluid (`typo3/sysext/fluid`, wrapping
`typo3fluid/fluid`) sit on top as the MVC/templating layer; several sysexts (belog,
beuser, form, indexed_search, …) are Extbase-based backend modules.
## Coding style
- PHP: PSR-12, 4-space indents, `declare(strict_types=1);`, one class per file under
`Classes/` matching the namespace, and the standard GPL file header (enforced by
`-s cglHeader`).
- JS/TS/SCSS: 2-space indents, ESLint and Stylelint configs in `Build/`.
- TypoScript/TSconfig: consistent casing and indentation, under `Configuration/` as in
existing extensions.
- `.editorconfig` is authoritative for indentation per file type (JSON uses tabs, RST
and Markdown wrap at 80 columns).
- Every thrown exception carries a **unique** integer code — the unix timestamp of the
moment it was written, e.g.
`throw new SiteNotFoundException('No site found …', 1521668882);`. Never copy a code
from existing code: `-s listExceptionCodes` runs
`Build/Scripts/duplicateExceptionCodeCheck.sh`, which reports duplicate and missing
codes.
- Deprecations are marked twice: a docblock line in the form
`@deprecated since v<current major>, will be removed in v<next major>` and a
`trigger_error('…', E_USER_DEPRECATED)` in the deprecated code path. Deprecate in
one major version, remove in the next, and never break API on a release branch.
See the changelog section for the required RST and extension-scanner entries.
- Labels live in `Resources/Private/Language/*.xlf`. Only the English source files are
edited here — translations are maintained by the translation teams on Crowdin and are
not part of this repository.
- Reference labels with the translation domain syntax
`<extkey>[.<subdir>].<resource>:<identifier>` instead of
`LLL:EXT:<ext>/Resources/Private/Language/locallang_<x>.xlf:<identifier>`. The
resource is the file name without `locallang_` and `.xlf`, `locallang.xlf` becomes
`messages`, a subdirectory adds a segment: `backend.toolbar:save`,
`core.form.tabs:…`. This holds wherever a label is resolved — `sL()`, TCA,
`Modules.php` (`'labels' => 'backend.modules.list'`) and Fluid
(`<f:translate key="core.bookmarks:title" />`). Converting existing references is a
cleanup of its own, not part of an unrelated patch. The syntax exists since v14.0,
so a backport to 13.4 needs the `LLL:EXT:` form.
- Do not silence new static-analysis findings in `Build/phpstan/phpstan-baseline.neon`;
the baseline is meant to shrink. Regenerate it (`-s phpstanGenerateBaseline`) only
after a PHPStan update.
## Testing guidelines
- Unit tests extend `TYPO3\TestingFramework\Core\Unit\UnitTestCase`; functional tests
extend `...\Core\Functional\FunctionalTestCase` and resolve services with
`$this->get(SomeClass::class)`.
- Test classes are `final`; use PHPUnit attributes (`#[Test]`, `#[DataProvider]`), not
docblock annotations. Unit/functional files end in `Test.php`.
- Fixture extensions go under `Tests/Functional/Fixtures/Extensions/`; DB fixtures are
CSV files loaded with `importCSVDataSet()`.
- Each functional test case gets a real TYPO3 instance provisioned below
`typo3temp/var/tests/`, which is why the suite is slow and why leftovers there can be
deleted at any time.
- `importCSVDataSet()` and the `pathsToProvideInTestInstance` keys do **not** resolve
`EXT:` paths — they are plain file lookups, so use `__DIR__`-relative paths.
- Prefer small, focused tests. Filter with `-- --filter <TestName>`.
## Changelog (RST) requirements
User-facing changes need an entry in
`typo3/sysext/core/Documentation/Changelog/<version>/`, where `<version>` is the
release currently in development on this branch. The authoritative source is
`\TYPO3\CMS\Core\Information\Typo3Version`: its `BRANCH` constant is the changelog
folder name and the current major for deprecation docblocks, and `VERSION` (e.g.
`15.0.0-dev`) names the exact release under development. The `branch-alias` in
`composer.json` mirrors the same information. Name it
`{Breaking,Deprecation,Feature,Important}-<issue>-<ShortDescription>.rst`.
- Scan neighbouring files in `Changelog/*/` and follow `Changelog/Howto.rst`.
- Start with `.. include:: /Includes.rst.txt`, then a reference label, then a headline
with an active phrasing whose `===` underline is **exactly** as long as the headline.
- Always a Description section and a closing `.. index::`. Sections are type-dependent:
everything except `Important` has "Impact"; `Deprecation` and `Breaking` additionally
need "Affected installations" and "Migration".
- Deprecations and breaking changes the extension scanner should detect must also be
referenced from `typo3/sysext/install/Configuration/ExtensionScanner/Php/` —
`-s checkExtensionScannerRst` verifies the link.
## Commits & review
**Only create or modify commits when explicitly asked.** Prefer to leave the commit
itself to the human, so they review what they submit.
- Reviews happen in Gerrit, not GitHub; the GitHub repo is a mirror and pull requests
are moved into Gerrit automatically.
- Run `composer gerrit:setup` once to install the commit-msg hook that adds the
`Change-Id` line. Omit that line when writing a message — the hook adds it. When
amending a commit that already has one, keep it byte-identical.
- Subject tags: `[BUGFIX]`, `[FEATURE]`, `[TASK]`, `[DOCS]`, `[SECURITY]`. Imperative,
concise subject; body explains rationale and impact. **Keep every line of the
message at 72 characters or less**, footer trailers included.
- The commit-msg hook reports longer lines, a missing subject tag and an incomplete
footer, but it does **not** abort the commit and still adds the `Change-Id`. Read
its output after every commit and fix reported problems with `git commit --amend`.
Use this footer order:
```
Resolves: #12345
Releases: main, 13.4
Change-Id: I0123456789abcdef0123456789abcdef01234567
```
At least one `Resolves:` line with a Forge issue number and `Releases:` are
mandatory (the hook only accepts `Resolves:`, not `Fixes:`). Add a `Resolves:`
line for every issue the change fixes; use `Related: #12345` for issues that
are connected to the change but not resolved by it.
- **Every commit needs its own Forge issue.** Do not reuse one issue number across the
commits of a series, and do not squash separate steps into one commit to avoid
opening issues: in Gerrit every commit is reviewed, merged and backported on its
own, and `Resolves:` is what closes the issue. The number is needed in two places —
the trailer and the RST changelog filename — so look it up (or ask for it) before
writing either. Prepare code and tests first, then get the number.
- `Releases:` lists the branches a change is meant for. Features go to `main` only;
bugfixes are backported to the maintained release branches. Push the change for the
branch that needs review first — normally `main` — with
`git push origin HEAD:refs/for/main`, and submit the backports once that patch is
agreed. Pushing the same change to several branches in parallel wastes review time
and usually has to be redone.
- Never add a `Signed-off-by:` trailer, not even when asked to commit. It certifies the
Developer Certificate of Origin (<https://developercertificate.org/>): a person's
statement that they have the right to submit the change and take responsibility for
it. Only the human can make it, when they commit or amend the change.
- Do not add `Co-authored-by:`, `Assisted-by:`, "Generated with …" or similar
attribution to commit messages, code, comments or documentation, and disable any
default attribution your tool adds.
- Write code for this change; do not reproduce code from other projects,
documentation, forums or training data. If a solution is adapted from an existing
source, tell the human the source and its licence.
- Before pushing: unit tests, functional tests, CGL and the JS build should be green.
## Security & configuration
- Never commit secrets; follow `SECURITY.md` for reporting vulnerabilities, and contact
[email protected] for potential security issues rather than filing them publicly.
- Local config and runtime data belong in `typo3conf/` and `typo3temp/` (not
versioned). Use the containers via `Build/Scripts/runTests.sh` for a reproducible
environment.
Guidance for coding agents working in the TYPO3 CMS Core monorepo. Personal or
machine-specific preferences do not belong here — put those in an untracked
CLAUDE.local.md (already gitignored).
typo3/sysext/, Build/Sources/.typo3/sysext/core/Documentation/, https://docs.typo3.org.curl -H "Accept: application/json" and the default curl user agent —
browser-like user agents hit the Anubis bot check. Useful endpoints:
issues/<id>.json (add ?include=journals for comments),
search.json?q=<query>, issues.json?project_id=27 (27 = TYPO3 Core).CONTRIBUTING.md and
https://docs.typo3.org/core-contribution../Build/Scripts/runTests.sh -s cglGit after committing.typo3/sysext/<ext>/ — the system extensions, each a Composer package
(typo3/cms-<ext>) with its own composer.json, a PSR-4 namespace under
TYPO3\CMS\... mapped in the root composer.json, and the same internal shape:
Classes/, Configuration/, Resources/{Private,Public}/,
Tests/{Unit,Functional}/, ext_localconf.php, ext_tables.sql, Documentation/.Build/ — all tooling: Scripts/ (test dispatcher + integrity checks), phpunit/,
phpstan/, php-cs-fixer/, Sources/ (frontend sources), tests/ (Playwright
specs and fixture extensions), Gruntfile.js, package.json.index.php — the single web entry point: SystemEnvironmentBuilder::run() →
Bootstrap::init() → Core\Http\Application, falling back to
Install\Http\Application when the full container cannot be built.typo3conf/, typo3temp/, vendor/ — local/generated, not versioned.Frontend sources live in Build/Sources/TypeScript/<ext-key>/ and
Build/Sources/Sass/; the build compiles them into
typo3/sysext/<ext>/Resources/Public/{JavaScript,Css}/. Never edit files under
Resources/Public/JavaScript or Resources/Public/Css — they are build output.
Mind the naming shift: TypeScript folders use dashes (rte-ckeditor), sysexts use
underscores (rte_ckeditor).
Everything runs through one container-based dispatcher,
./Build/Scripts/runTests.sh (podman by default, -b docker to switch). Do not
invoke phpunit, phpstan, php-cs-fixer, npm or grunt directly — the wrapper
supplies the PHP version, database service and bootstrap. -h is the authoritative,
always-current list of suites and options; the examples below are a curated subset.
# PHP tests (-p selects the PHP version; -h shows which ones this branch accepts)
CI=true ./Build/Scripts/runTests.sh -s unit
CI=true ./Build/Scripts/runTests.sh -s unit typo3/sysext/core/Tests/Unit/RegistryTest.php
CI=true ./Build/Scripts/runTests.sh -s unit -- --filter someTestMethodName
CI=true ./Build/Scripts/runTests.sh -s unitRandom # random order, exposes test isolation bugs
CI=true ./Build/Scripts/runTests.sh -s functional -d sqlite typo3/sysext/core/Tests/Functional/Database
CI=true ./Build/Scripts/runTests.sh -s functional -d postgres -i 17 # also: mariadb, mysql
CI=true ./Build/Scripts/runTests.sh -s functional -c 3/13 # run chunk 3 of 13
# static analysis / style
./Build/Scripts/runTests.sh -s cgl # fix; add -n for dry-run
./Build/Scripts/runTests.sh -s cglGit # CGL of the last commit only
./Build/Scripts/runTests.sh -s phpstan # custom rules, baseline in Build/phpstan/
./Build/Scripts/runTests.sh -s checkIntegrityPhp # registered PHP integrity rules
./Build/Scripts/runTests.sh -s listExceptionCodes # also reports duplicate/missing exception codes
# frontend
CI=true ./Build/Scripts/runTests.sh -s build # TypeScript + Sass + assets
./Build/Scripts/runTests.sh -s unitJavascript # web-test-runner
./Build/Scripts/runTests.sh -s lintTypescript # add -- --fix
./Build/Scripts/runTests.sh -s lintScss
./Build/Scripts/runTests.sh -s npm -- ci
# end-to-end (Playwright)
./Build/Scripts/runTests.sh -s e2e
./Build/Scripts/runTests.sh -s e2e Build/tests/playwright/e2e/form-engine/elements-rte-source-editing.spec.ts
./Build/Scripts/runTests.sh -s e2e -- --grep login
./Build/Scripts/runTests.sh -s e2e-install -d sqlite
./Build/Scripts/runTests.sh -s e2e-prepare # boot an instance for manual poking
./Build/Scripts/runTests.sh -s e2e-browser # same specs with the GUI on http://127.0.0.1:43837
# docs / integrity gates that CI enforces (more check*/lint* suites via -h)
./Build/Scripts/runTests.sh -s checkRst
./Build/Scripts/runTests.sh -s watchRst core interactive # live RST preview / changelog wizard
./Build/Scripts/runTests.sh -s checkExtensionScannerRst
./Build/Scripts/runTests.sh -s lintServicesYaml
./Build/Scripts/runTests.sh -s lintYaml # all YAML except Services.yaml
./Build/Scripts/runTests.sh -s checkIntegrityXliff # add -s normalizeXliff to fix .xlf formatting
# composer inside the container
./Build/Scripts/runTests.sh -s composer -- install
Functional tests default to SQLite, which is by far the fastest feedback loop; only switch DBMS when the change touches SQL/Doctrine specifics.
Two options worth knowing: -x forwards xdebug to a listening IDE (port 9003, change
with -y), and -u updates the typo3/core-testing-* images — the first thing to try
when a suite fails in ways the code cannot explain.
These five mechanisms explain most of the codebase.
1. Packages + bootstrap. Core\Package\PackageManager discovers active
extensions; Core\Core\Bootstrap builds a Symfony DI container that is compiled and
cached. Extensions contribute via Configuration/Services.yaml (+ optional
Services.php for compiler passes). A second, minimal failsafe container exists for
the install tool / broken configuration, assembled from the handful of
Classes/ServiceProvider.php files (core, backend, install, fluid, dashboard) — code
that must work in the installer needs manual wiring there, autowiring does not apply.
2. Dependency injection. Services.yaml uses
autowire: true, autoconfigure: true, public: false with a resource: '../Classes/*'
sweep, so classes are autowired by default; only entries needing tags, factories or
non-shared behaviour are listed explicitly. Extension points are wired with tagged
services (e.g. mfa.provider, softreference.parser, fal.file_renderer) consumed
via !tagged_iterator. Prefer constructor injection —
GeneralUtility::makeInstance() is legacy and is actively being reduced.
Fluid ViewHelpers take part in DI as well: a compiler pass in
fluid/Configuration/Services.php makes every ViewHelperInterface public and
non-shared, so ViewHelperResolver resolves them through the container. Inject only
stateless singletons (never ContentObjectRenderer, Extbase's UriBuilder, Context
or other request-scoped objects), never from static methods, and remember the
failsafe container: a ViewHelper used in install-tool templates that gains required
constructor arguments must be wired by hand in install/Classes/ServiceProvider.php.
3. PSR-15 request pipeline. Each request type (frontend, backend) is a
middleware stack declared per extension in Configuration/RequestMiddlewares.php,
with before/after ordering keys resolved by Http\MiddlewareStackResolver and run
by Http\MiddlewareDispatcher. Backend routes come from
Configuration/Backend/{Routes,AjaxRoutes,Modules}.php; frontend routing is
site-configuration driven (Core\Routing, Core\Site).
4. PSR-14 events. Hooks are legacy; new extension points are events dispatched
through Core\EventDispatcher. Register listeners with the #[AsEventListener]
attribute (Core\Attribute\AsEventListener) or an event.listener service tag. When
replacing a hook, add both the event class and a Feature-*.rst changelog entry.
5. TCA and the schema layer. Table/field configuration lives in
Configuration/TCA/<table>.php (own tables) and
Configuration/TCA/Overrides/<table>.php (additions to foreign tables). Do not read
$GLOBALS['TCA'] in new code: Core\Schema\TcaSchemaFactory turns TCA into typed
TcaSchema / field / capability / relation objects, and that API is what new code
should consume. Core\DataHandling\DataHandler remains the central write path for
records (versioning, workspaces, localization).
Extbase (typo3/sysext/extbase) and Fluid (typo3/sysext/fluid, wrapping
typo3fluid/fluid) sit on top as the MVC/templating layer; several sysexts (belog,
beuser, form, indexed_search, …) are Extbase-based backend modules.
declare(strict_types=1);, one class per file under
Classes/ matching the namespace, and the standard GPL file header (enforced by
-s cglHeader).Build/.Configuration/ as in
existing extensions..editorconfig is authoritative for indentation per file type (JSON uses tabs, RST
and Markdown wrap at 80 columns).throw new SiteNotFoundException('No site found …', 1521668882);. Never copy a code
from existing code: -s listExceptionCodes runs
Build/Scripts/duplicateExceptionCodeCheck.sh, which reports duplicate and missing
codes.@deprecated since v<current major>, will be removed in v<next major> and a
trigger_error('…', E_USER_DEPRECATED) in the deprecated code path. Deprecate in
one major version, remove in the next, and never break API on a release branch.
See the changelog section for the required RST and extension-scanner entries.Resources/Private/Language/*.xlf. Only the English source files are
edited here — translations are maintained by the translation teams on Crowdin and are
not part of this repository.<extkey>[.<subdir>].<resource>:<identifier> instead of
LLL:EXT:<ext>/Resources/Private/Language/locallang_<x>.xlf:<identifier>. The
resource is the file name without locallang_ and .xlf, locallang.xlf becomes
messages, a subdirectory adds a segment: backend.toolbar:save,
core.form.tabs:…. This holds wherever a label is resolved — sL(), TCA,
Modules.php ('labels' => 'backend.modules.list') and Fluid
(<f:translate key="core.bookmarks:title" />). Converting existing references is a
cleanup of its own, not part of an unrelated patch. The syntax exists since v14.0,
so a backport to 13.4 needs the LLL:EXT: form.Build/phpstan/phpstan-baseline.neon;
the baseline is meant to shrink. Regenerate it (-s phpstanGenerateBaseline) only
after a PHPStan update.TYPO3\TestingFramework\Core\Unit\UnitTestCase; functional tests
extend ...\Core\Functional\FunctionalTestCase and resolve services with
$this->get(SomeClass::class).final; use PHPUnit attributes (#[Test], #[DataProvider]), not
docblock annotations. Unit/functional files end in Test.php.Tests/Functional/Fixtures/Extensions/; DB fixtures are
CSV files loaded with importCSVDataSet().typo3temp/var/tests/, which is why the suite is slow and why leftovers there can be
deleted at any time.importCSVDataSet() and the pathsToProvideInTestInstance keys do not resolve
EXT: paths — they are plain file lookups, so use __DIR__-relative paths.-- --filter <TestName>.User-facing changes need an entry in
typo3/sysext/core/Documentation/Changelog/<version>/, where <version> is the
release currently in development on this branch. The authoritative source is
\TYPO3\CMS\Core\Information\Typo3Version: its BRANCH constant is the changelog
folder name and the current major for deprecation docblocks, and VERSION (e.g.
15.0.0-dev) names the exact release under development. The branch-alias in
composer.json mirrors the same information. Name it
{Breaking,Deprecation,Feature,Important}-<issue>-<ShortDescription>.rst.
Changelog/*/ and follow Changelog/Howto.rst... include:: /Includes.rst.txt, then a reference label, then a headline
with an active phrasing whose === underline is exactly as long as the headline... index::. Sections are type-dependent:
everything except Important has "Impact"; Deprecation and Breaking additionally
need "Affected installations" and "Migration".typo3/sysext/install/Configuration/ExtensionScanner/Php/ —
-s checkExtensionScannerRst verifies the link.Only create or modify commits when explicitly asked. Prefer to leave the commit itself to the human, so they review what they submit.
composer gerrit:setup once to install the commit-msg hook that adds the
Change-Id line. Omit that line when writing a message — the hook adds it. When
amending a commit that already has one, keep it byte-identical.[BUGFIX], [FEATURE], [TASK], [DOCS], [SECURITY]. Imperative,
concise subject; body explains rationale and impact. Keep every line of the
message at 72 characters or less, footer trailers included.Change-Id. Read
its output after every commit and fix reported problems with git commit --amend.
Use this footer order: Resolves: #12345
Releases: main, 13.4
Change-Id: I0123456789abcdef0123456789abcdef01234567
At least one Resolves: line with a Forge issue number and Releases: are
mandatory (the hook only accepts Resolves:, not Fixes:). Add a Resolves:
line for every issue the change fixes; use Related: #12345 for issues that
are connected to the change but not resolved by it.
Resolves: is what closes the issue. The number is needed in two places —
the trailer and the RST changelog filename — so look it up (or ask for it) before
writing either. Prepare code and tests first, then get the number.Releases: lists the branches a change is meant for. Features go to main only;
bugfixes are backported to the maintained release branches. Push the change for the
branch that needs review first — normally main — with
git push origin HEAD:refs/for/main, and submit the backports once that patch is
agreed. Pushing the same change to several branches in parallel wastes review time
and usually has to be redone.Signed-off-by: trailer, not even when asked to commit. It certifies the
Developer Certificate of Origin (https://developercertificate.org/): a person's
statement that they have the right to submit the change and take responsibility for
it. Only the human can make it, when they commit or amend the change.Co-authored-by:, Assisted-by:, "Generated with …" or similar
attribution to commit messages, code, comments or documentation, and disable any
default attribution your tool adds.SECURITY.md for reporting vulnerabilities, and contact
[email protected] for potential security issues rather than filing them publicly.typo3conf/ and typo3temp/ (not
versioned). Use the containers via Build/Scripts/runTests.sh for a reproducible
environment.How can I help you explore Laravel packages today?