Common tasks in Extbase 

This page answers the "how do I…" questions that come up repeatedly when building or maintaining Extbase extensions. Each entry states the goal, gives a short direct answer, and links to the full explanation.

If you know what you want to achieve but are not sure which chapter covers it, start here.

Add a field to a third-party extension's model 

Goal: A third-party extension (for example georgringer/news ) has a model you want to extend with an extra database field — without forking the extension.

Short answer: You need three things working together: a TCA override that adds the column to the table, a class mapping override in your own extension that tells Extbase to use your subclass instead of the original, and your subclass extending the third-party model with the new property and getter/setter. No changes to the original extension required.

Extension load order matters here. The class mapping in Configuration/Extbase/Persistence/Classes.php and any DI alias (used by tools like friendsoftypo3/extension-builder or evoweb/extender ) must be processed after the original extension registers its own mapping. Declare the third-party extension as a dependency in your composer.json to guarantee this:

EXT:my_extension/composer.json (excerpt)
{
  "require": {
    "georgringer/news": "^11.0"
  }
}
Copied!

Without this, the load order is undefined and your class mapping may be silently overwritten by the original.

Query records with custom conditions 

Goal: findAll() and findBy() are not enough — you need records filtered by date, a relation, or a combination of conditions.

Short answer: Call $this->createQuery() in your repository method, build constraints with $query->matching() , and return $query->execute() . For anything the Extbase query API cannot express (aggregates, complex joins), drop down to ConnectionPool and raw DBAL.

Use a repository in a controller 

Goal: Make a repository available inside a controller action without using GeneralUtility::makeInstance() .

Short answer: Declare the repository as a constructor parameter with protected readonly . TYPO3's DI container injects it automatically — no annotation, no factory call needed.

EXT:my_extension/Classes/Controller/BlogPostController.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\BlogPostRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogPostController extends ActionController
{
  public function __construct(
    protected readonly BlogPostRepository $blogPostRepository,
  ) {}
}
Copied!

Set a default sort order for all repository queries 

Goal: Every call to findAll() or findBy() on a repository should return records sorted by a specific property, without having to specify ordering in every call.

Short answer: Set the $defaultOrderings class property on your repository. It applies automatically to all queries from that repository.

EXT:my_extension/Classes/Domain/Repository/BlogPostRepository.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

class BlogPostRepository extends Repository
{
  protected $defaultOrderings = [
    'publishDate' => QueryInterface::ORDER_ASCENDING,
  ];
}
Copied!

Load a relation only when needed 

Goal: A model has a related object or collection that is expensive to load and not always needed — for example, the comments on an event in a list view. You want to avoid loading them unless the template actually uses them.

Short answer: Add #[Lazy] to the relation property. Extbase defers the database query until the property is first accessed. The property type and the getter stay the same as for an eager relation.

Delete related objects when the parent is deleted 

Goal: When an event is deleted, its related comment records should be deleted automatically rather than left as orphans in the database.

Short answer: Add #[Cascade('remove')] to the relation property on the owning model. Extbase will delete the related objects through their repository when the parent is deleted.

Use a native PHP enum as a model property 

Goal: A model property should hold one of a fixed set of values — for example a status or salutation — and you want to use a native PHP 8.1 backed enum (an enum with an underlying string or int value that can be stored in the database) rather than a plain string or integer.

Short answer: Declare the property with the enum type and a default case. Extbase's built-in EnumConverter handles the conversion between the stored backing value and the enum instance automatically. No extra configuration needed.

Add a computed property that is never stored in the database 

Goal: A model needs a property that holds a computed or temporary value — for example a formatted label derived from other properties — that should never be written to the database.

Short answer: Add #[Transient] to the property. Extbase skips it entirely during read and write operations.

Let editors set plugin settings in a content block FlexForm 

Goal: Expose a plugin setting (for example "items per page") as an editable field in a content element, so that an editor can change it without having to write a FlexForm data structure by hand.

Short answer: When you register an Extbase plugin as a Content Block that is rendered through an EXTBASEPLUGIN and reuses the pi_flexform field, a FlexForm field with an identifier like settings.<name> will be converted into $this->settings['<name>'] — exactly like a TypoScript setting. The reusable pages and recursive fields likewise feed persistence.storagePid and persistence.recursive . This is a little-known mechanism and is becoming more relevant as Content Block technology moves into the Core.