---
title: "Common pitfalls in Extbase"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-appendix-pitfalls@main"
source: "ExtensionArchitecture/Extbase/Appendix/CommonPitfalls.rst"
rendered: "2026-09-20T15:52:37+00:00"
---

# Common pitfalls in Extbase {#extbase-appendix-pitfalls}

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.

**On this page**

-   [Model properties declared private are never populated](https://docs.typo3.org/permalink/t3coreapi:model-properties-declared-private-are-never-populated@main)
-   [findAll() returns nothing on an Extbase repository](https://docs.typo3.org/permalink/t3coreapi:findall-returns-nothing-on-an-extbase-repository@main)
-   [Annotations silently ignored in TYPO3 v14](https://docs.typo3.org/permalink/t3coreapi:annotations-silently-ignored-in-typo3-v14@main)
-   [Magic findBy*() methods removed in TYPO3 v14](https://docs.typo3.org/permalink/t3coreapi:magic-findby-methods-removed-in-typo3-v14@main)
-   [Plugin registered with list_type no longer works](https://docs.typo3.org/permalink/t3coreapi:plugin-registered-with-list-type-no-longer-works@main)
-   [AbstractValueObject is not public API](https://docs.typo3.org/permalink/t3coreapi:abstractvalueobject-is-not-public-api@main)
-   [Frontend form with inline relations produces incomplete saves or silent data loss](https://docs.typo3.org/permalink/t3coreapi:frontend-form-with-inline-relations-produces-incomplete-saves-or-silent-data-loss@main)
-   [Property mapping denied: form fields not saved without a trusted-properties token](https://docs.typo3.org/permalink/t3coreapi:property-mapping-denied-form-fields-not-saved-without-a-trusted-properties-token@main)
-   [Template variable renders empty in a Fluid template](https://docs.typo3.org/permalink/t3coreapi:template-variable-renders-empty-in-a-fluid-template@main)
-   [f:debug produces no output](https://docs.typo3.org/permalink/t3coreapi:f-debug-produces-no-output@main)
-   [Template file not found, or wrong template rendered](https://docs.typo3.org/permalink/t3coreapi:template-file-not-found-or-wrong-template-rendered@main)
-   [Extbase model validation and TCA validation are independent](https://docs.typo3.org/permalink/t3coreapi:extbase-model-validation-and-tca-validation-are-independent@main)
-   [A global config.tx_extbase value breaks an unrelated plugin](https://docs.typo3.org/permalink/t3coreapi:a-global-config-tx-extbase-value-breaks-an-unrelated-plugin@main)
-   [A blank FlexForm field silently overrides the TypoScript default](https://docs.typo3.org/permalink/t3coreapi:a-blank-flexform-field-silently-overrides-the-typoscript-default@main)

## Model properties declared private are never populated {#extbase-model-properties-default-values-cpp}

**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.

> [!NOTE]
> **See also**
>
> [Defining properties](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-model-properties@main).

## `findAll()` returns nothing on an Extbase repository {#extbase-appendix-pitfalls-storagepid}

**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.

> [!NOTE]
> **See also**
>
> [The storagePid](https://docs.typo3.org/permalink/t3coreapi:extbase-persistence-storagepid@main) — the page restriction explained in full: the resolution chain, the recursive setting, and how to override or drop it.

## Annotations silently ignored in TYPO3 v14 {#extbase-appendix-pitfalls-annotations}

**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.

> [!NOTE]
> **See also**
>
> -   [Annotations replaced by PHP attributes (TYPO3 v12 / required from v14)](https://docs.typo3.org/permalink/t3coreapi:extbase-upgrading-annotations-to-attributes@main) — migration steps and
>
> the full before/after example.
>
> -   [PHP attributes — the v14 way](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-model-attributes@main)
> -   [Extbase PHP attributes reference](https://docs.typo3.org/permalink/t3coreapi:extbase-appendix-attributes@main).

## Magic `findBy`\*() methods removed in TYPO3 v14 {#extbase-appendix-pitfalls-magic-findby}

**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.

> [!NOTE]
> **See also**
>
> [Magic findBy(), \`findOneBy\`(), countBy\*() methods removed (TYPO3 v14)](https://docs.typo3.org/permalink/t3coreapi:extbase-upgrading-magic-findby@main) — migration table with before/after
> examples.
>
> [Built-in find methods](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-repository-find-methods@main) — the current find method reference.

## Plugin registered with list_type no longer works {#extbase-appendix-pitfalls-list-type}

**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.

> [!NOTE]
> **See also**
>
> [Frontend plugin registration](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-frontend-plugin@main) — covers the v14
> registration approach and the upgrade wizard required for existing records.

## `AbstractValueObject` is not public API {#extbase-appendix-pitfalls-abstract-value-object}

**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.

> [!NOTE]
> **See also**
>
> [Value objects](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-model-value-objects@main).

## Frontend form with inline relations produces incomplete saves or silent data loss {#extbase-appendix-pitfalls-frontend-forms-relations}

**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.

## Property mapping denied: form fields not saved without a trusted-properties token {#extbase-appendix-pitfalls-property-mapping-denied}

**Symptom:** A domain object argument arrives in an action with all properties
set to default values even though the form or URL contains data. No error
or validation failure is shown.

**Why:** When a request does not carry a `__trustedProperties` token,
Extbase denies all properties by default to prevent
[mass assignment](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/20-Testing_for_Mass_Assignment)
attacks. A token is generated automatically by `<f:form>` and covers
the fields rendered in the form exactly. Requests that bypass this — URL
parameters, custom forms without a token, or JSON payloads — don't carry a token,
so properties aren't allowed unless a controller explicitly permits it.

**What to do:** If the request will never carry a `__trustedProperties`
token, add an `initialize*Action()` method and call
`allowProperties()` (or `allowAllProperties()`) on the relevant
argument's property mapping configuration.

> [!NOTE]
> **See also**
>
> [Manually allowing properties on Extbase action arguments](https://docs.typo3.org/permalink/t3coreapi:extbase-controller-propertymapping-allowproperties@main)
> — how to write the initializer and which methods to use.

## Template variable renders empty in a Fluid template {#extbase-appendix-pitfalls-template-empty}

**Symptom:** A variable or property path in a Fluid template has no output.
No error is thrown and there is no exception in the log. The surrounding HTML
renders normally; only the value is blank.

**Why:** Fluid silently renders an empty string when property resolution fails.
Resolution is attempted in the following order: the public property, `getX()`,
`hasX()`, `isX()`. If none of these exist or they are
accessible, Fluid gives up without raising an error. Common causes:

-   A typo in the property name — `{conference.titel}` instead of
    `{conference.title}` silently produces nothing.
-   A typo in an array key — for arrays, Fluid resolves `{data.title}`
    via `$data['title']`. A missing or misspelled key produces nothing,
    just like a missing property on an object.
-   A `private` property without a public getter — Fluid cannot read a
    `private` property directly. Add a public `getX()` method and
    Fluid will find it regardless of property visibility.
-   A missing getter — a `protected` property without a corresponding
    `getX()` method is invisible to Fluid.
-   The variable was not assigned in the controller — `assign()` was not
    called, or was called under a different name.
-   The variable was not passed to a partial — variables assigned in the
    controller are available in the template, but partials only receive what is
    explicitly passed via `<f:render partial="..." arguments="{conference: conference}" />`.
    Pass each variable by name, or use `arguments="{_all}"` to forward
    everything the template has. `{_all}` is convenient and mostly fine —
    partials tend to grow and need more input over time — but be aware it makes
    the partial's dependencies implicit.
-   A missing TCA column definition — Extbase only hydrates properties that
    have a corresponding column in `$GLOBALS['TCA']`. A model property
    with no TCA column is silently skipped during loading and stays at its
    default value.

**What to do:** Check the exact property name and visibility. Add a
public `getX()` method if one is missing. In the controller, verify that
`$this->view->assign('conference', $conference)` is called with the
correct variable name. In the template itself, use
[f:debug](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Debug.html#typo3-fluid-debug)
to dump the value at the point of use:

**EXT:my_extension/Resources/Private/Templates/Conference/Show.fluid.html**

```html
<f:debug>{conference}</f:debug>
```

This renders a formatted dump of the variable, including its type and
accessible properties.

## f:debug produces no output {#extbase-appendix-pitfalls-fdebug-no-output}

**Symptom:** `<f:debug>{variable}</f:debug>` is in the template but
nothing appears on the page.

**Why:** There are 2 possible causes and both are common:

-   **Cached output:** Extbase plugin output is cached by default. If the
    page is cached, the rendered HTML — without the debug dump — is served from
    the cache. The ViewHelper will not run again until the cache is cleared.
    Clear the page cache in the TYPO3 backend or temporarily make the plugin
    non-cacheable to force the page to be rerendered.
-   **Production Application Context suppresses debug output:** Default TYPO3
    installations run in a [Production context](https://docs.typo3.org/permalink/t3coreapi:application-context@main). The
    `ProductionErrorHandler` deliberately suppresses debug output to
    avoid leaking internal information to site visitors. In a Production context,
    `f:debug` renders nothing.

    The Application Context must be set via an environment variable or webserver
    configuration — it cannot be changed from inside TYPO3. See
    [Set the ApplicationContext](https://docs.typo3.org/permalink/t3coreapi:set-application-context@main) for all available methods. To check which
    context is currently active, open **System > Environment > Environment
    overview** in the TYPO3 backend.
-   **Output is prepended to the page, not rendered in place:** By default
    `f:debug` prepends its output to the top of the DOM rather than
    rendering at the point of use. This means the dump is easy to miss if you
    are looking at a specific section of the page, and it is invisible in JSON
    views or when JavaScript consumes the output. Use `inline="1"` to
    render the dump exactly where the tag appears:

    ```html
    <f:debug inline="1">{conference}</f:debug>
    ```

> [!NOTE]
> **See also**
>
> [How Fluid accesses object properties](https://docs.typo3.org/permalink/t3coreapi:extbase-view-property-access@main)
> for the full resolution order and why `private` properties are
> never accessible.

## Template file not found, or wrong template rendered {#extbase-appendix-pitfalls-template-not-found}

**Symptom:** Extbase throws a "Could not find template file" exception, or
renders a template from a different path than expected — for example, a
customised template is ignored and the original one is used instead.

**Why:** Template resolution is based on a numerically keyed path array
searched from the highest key downward. Several things can go wrong:

-   **Wrong key order:** a customisation registered at key `5` is
    ignored in favour of the original at key `10`, because
    `10` is higher and wins.
-   **Case mismatch on Linux:** `List.fluid.html` and
    `list.fluid.html` are different files. The convention requires an
    uppercase first letter for both the controller subdirectory and the action
    file name.
-   **Controller subdirectory name mismatch:** the subdirectory must match the
    controller class name without the `Controller` suffix —
    `ConferenceController` requires `Conference/`, not
    `Conferences/` or `conference/`.
-   **Path array key too low:** the overriding path is registered at a key
    lower than the original (for example `5` vs `10`),
    so the original wins. The numeric key is the only thing that determines
    precedence — use a key higher than whatever the original extension uses.

**What to do:** Use the Active TypoScript module in the TYPO3 backend to
inspect the computed `plugin.tx_<extensionkey>.view` paths and
their keys. Verify that file and subdirectory names match the convention
exactly. Check that the overriding extension declares a dependency on the
original in `composer.json`.

> [!NOTE]
> **See also**
>
> -   [Fluid template file resolution in Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-view-templates@main)
>     — naming convention, default paths, and key ordering.
> -   [Overriding Fluid templates from a third-party extension](https://docs.typo3.org/permalink/t3coreapi:extbase-view-third-party-override@main)
>     — extension loading order and path registration.

## Extbase model validation and TCA validation are independent {#extbase-appendix-pitfalls-validation-tca-gap}

**Symptom:** A record is created or edited in the TYPO3 backend 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 runs during frontend request processing; TCA
validation runs in the backend form engine. There is no shared layer that
enforces both.

This gap is most painful when multiple Extbase models map to the same database
table. There may be different models carrying different validation rules for a
table, but the backend will always use 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. If a constraint is important in both contexts, add it
both as a `#[Validate]` attribute on the model property and as a
corresponding TCA column configuration. For records that must be valid in both
contexts, test both paths.

If a form requires different or stricter validation than the domain model allows
— for example, a multi-step form that validates partial state — consider using a
DTO. This is a plain PHP class that includes only
form fields with their validation rules and is separate from the persisted
domain model. DTOs are validated by Extbase. Only successfully validated DTOs are
mapped to models and persisted.

> [!NOTE]
> **See also**
>
> [Validation in Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-validation-overview@main)
> — lifecycle, `#[Validate]` placement, and how `errorAction()` is triggered.

## A global config.tx_extbase value breaks an unrelated plugin {#extbase-appendix-pitfalls-global-config}

**Symptom:** A third-party Extbase plugin that works in a clean installation
misbehaves in a particular TYPO3 instance. A list that should fall back to its
default view returns a 404 for a mistyped action; a detail action that used to
throw a catchable exception now displays "page not found". Nothing
in the extension's TypoScript explains the change, and its code is
unmodified. The same extension behaves correctly in a different installation.

**Why:** Somewhere in the site's TypoScript — typically in a site package — a
framework setting is in the global `config.tx_extbase` scope
instead of in a plugin scope. Because `config.tx_extbase` is the
lowest-precedence layer that applies to **every** Extbase plugin in the
frontend, a value intended to fix one extension silently reconfigures all of
them. A common trigger is copying an MVC error-handling line such as

```typoscript
config.tx_extbase.mvc.throwPageNotFoundExceptionIfActionCantBeResolved = 1
```

from a snippet meant for a single plugin. Every installed Extbase plugin that
relied on the default behaviour — fall back to the first registered action —
now returns a 404 instead, including plugins whose authors deliberately depend
on that graceful fallback.

**What to do:** Inspect the computed configuration in the
**Active TypoScript** backend module for both
`config.tx_extbase` and the affected
`plugin.tx_<extensionkey>` tree. Move any framework setting that was
meant for one extension down to that plugin's scope. Reserve
`config.tx_extbase` for a genuine installation-wide policy, and if
a single plugin differs from this, override the key under its own
`plugin.tx_<extensionkey>_<pluginname>` scope, which will take
precedence over the global one.

> [!NOTE]
> **See also**
>
> [The global scope: config.tx_extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-configuration-typoscript-global-scope@main)
> — what the global scope is for and why it should be used sparingly.

## A blank FlexForm field silently overrides the TypoScript default {#extbase-appendix-pitfalls-flexform-empty-overrides}

**Symptom:** A setting works until a plugin is added to a page, where it takes
the controller's hard-coded fallback instead of the TypoScript value. A
concrete case: `settings.itemsPerPage` is set to `35` in
TypoScript, the controller falls back to `20` if the setting is missing, and
the plugin's FlexForm also exposes an "Items per page" field. The editor never
touches the field. The list paginates at **20** items per page — the
controller fallback — therefore ignoring the `35` that should apply.

**Why:** FlexForm values override TypoScript field by field, and a FlexForm
field that an editor leaves blank is still stored and participates in the
override — but obviously only as an empty value. So `$this->settings['itemsPerPage']` arrives
as an empty string rather than `35`. The controller then sees an empty value, treats
it as "not set", and applies its own fallback of `20`. Nothing errors; the
configured `35` is overwritten before it gets to the controller.

**What to do:** add the field to
`ignoreFlexFormSettingsIfEmpty` so that it does not
overrule the TypoScript default if it is empty:

**EXT:my_extension/Configuration/Sets/MyExtension/setup.typoscript**

```typoscript
plugin.tx_myextension_conferencelist.ignoreFlexFormSettingsIfEmpty = itemsPerPage
```

With this set, the empty FlexForm value will be dropped before the merge and
the TypoScript `35` will survive. In this way you can add every FlexForm field that mirrors a
TypoScript default the editor may legitimately leave blank.

> [!NOTE]
> **See also**
>
> [Output format, language overrides and FlexForm handling](https://docs.typo3.org/permalink/t3coreapi:extbase-configuration-typoscript-other@main)
> — the `ignoreFlexFormSettingsIfEmpty` reference.
