Common pitfalls in Extbase 

This page collects the situations that most commonly trip up Extbase developers — from beginners hitting their first wall to experienced developers upgrading from older TYPO3 versions. Each entry names the symptom, explains briefly why it happens, and points to the full discussion.

If something in your extension is not working and you are not sure why, scan this page first.

Model properties declared private are never populated 

Symptom: A model property always holds its default value after loading from the database, even though the database column contains data. No error is thrown.

Why: Extbase hydrates properties via _setProperty(), a method defined on AbstractDomainObject. It assigns values using dynamic property access ( $this->{$propertyName} = $value). PHP's visibility rules prevent a parent class method from writing to a private property declared in a child class — the assignment silently does nothing. The same applies in the other direction: dirty-state tracking cannot read private properties either, so changes are never persisted.

This catches developers who follow the general good-practice rule of keeping properties as private as possible. In Extbase models, protected is the correct visibility — not private. Public properties also work and are a valid, shorter alternative (no getters or setters required), but they bypass lazy-loading proxies and dirty-state tracking, which can matter for relations.

findAll() returns nothing on an Extbase repository 

Symptom: $repository->findAll() (or any repository query) returns an empty result, but the records clearly exist in the database.

Why: Every repository query is filtered to one or more storage pages (the storagePid) by default. If no storage page is configured, or the records live on a different page than expected, the query returns nothing. findByUid() is the only method that ignores storagePid.

Annotations silently ignored in TYPO3 v14 

Symptom: Lazy loading, cascade delete, or validation rules defined in DocBlock annotations ( @Extbase\ORM\Lazy, @Extbase\Validate, etc.) have no effect. No error is thrown.

Why: DocBlock annotation support was removed in TYPO3 v14. Extbase simply ignores them. The replacement is native PHP attributes.

Magic findBy*() methods removed in TYPO3 v14 

Symptom: Calls to findByTitle($value), findOneBySlug($value), or countByStatus($value) throw an error or do nothing after upgrading to TYPO3 v14.

Why: The magic property-name methods were deprecated in v12.3 and removed in v14. The replacements use an explicit array signature.

Plugin registered with list_type no longer works 

Symptom: An existing plugin content element stops rendering after upgrading to TYPO3 v14, or a newly registered plugin cannot be selected in the backend.

Why: The list_type / "General Plugin" content element was deprecated in v13.4 and removed in v14. Plugins must now be registered as dedicated CType content elements.

AbstractValueObject is not public API 

Symptom: Code extending \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject produces deprecation warnings or breaks unexpectedly.

Why: The class is marked @internal in v14 — it is not part of the public Extbase API and may change or be removed without notice. No replacement class is provided.

What to do instead: Implement value objects as plain PHP classes. The DDD concept is valid; the base class is not.

Frontend form with inline relations produces incomplete saves or silent data loss 

Symptom: A frontend form that creates or updates a model with inline relations (speakers, images, tags added dynamically) produces incomplete saves, orphaned records, or silent data loss for dynamically added fields that fail HMAC argument hash validation. Trying to replicate the backend's "add another row" UX via JavaScript makes things worse.

Why: Two independent problems compound each other. First, Extbase's property mapping and persistence layer were not designed to handle a graph of new and modified related objects submitted in a single form — partial saves and inconsistent state are the common outcome. Second, TYPO3's HMAC-based argument hashing protects against mass-assignment attacks by signing the field names known at render time; dynamically generated field names are not covered and either fail or require disabling the protection entirely.

What to do instead: Avoid the pattern rather than work around it. Concrete alternatives: split the form into single-object steps; manage relations in a backend module where the tooling is designed for it; use separate AJAX endpoints that create one related record at a time; consider DataHandler for write operations that need IRRE-style relation management.

Extbase model validation and TCA validation are independent 

Symptom: A record created or edited in the TYPO3 backend passes without error, but the same record fails Extbase validation in the frontend — or vice versa. A backend editor saves a record that a frontend action then rejects immediately. Or a record saved through Extbase arrives in the backend in a state the backend form cannot open cleanly.

Why: Extbase validation (#[Validate] attributes on model properties) and TCA validation (eval, required, and similar TCA column configuration) are entirely separate systems. Neither one knows about the other. Extbase validation only runs during frontend request processing; TCA validation only runs in the backend form engine. There is no shared layer that enforces both at once.

This gap is most painful when multiple Extbase models map to the same database table — each model may carry different validation rules, but the backend always uses the TCA for that table regardless of which model class is involved.

What to do instead: Treat the two systems as complementary and configure both deliberately. For every constraint that matters in both contexts, add it both as a #[Validate] attribute on the model property and as the corresponding TCA column configuration. For records that must be valid in both contexts, test both paths explicitly.

When a form needs stricter or different validation than the domain model allows — for example, a multi-step form that validates partial state — consider using a DTO: a plain PHP class that carries only the form fields and their validation rules, separate from the persisted domain model. The DTO is validated by Extbase; only a successfully validated DTO is mapped to the model and persisted.