Extbase quick start for experienced developers
You know TYPO3, you know PHP, you just want the steps. This page gets a minimal but fully working Extbase extension in front of you as quickly as possible — a list and detail view for a custom record type, registered as a frontend plugin.
For the reasoning behind each step, follow the links into the relevant chapters.
On this page
Step 1: Scaffold the extension
Use the FriendsOfTYPO3 kickstarter package to generate the extension skeleton:
composer require friendsoftypo3/kickstarter --dev
vendor/bin/typo3 make:extension
Answer the prompts (vendor name, extension key, etc.). The kickstarter generates
the directory structure, composer. and the boilerplate files you need.
Since TYPO3 v14 you do not need ext_ unless you plan to publish
your extension to the TYPO3 Extension Repository.
See also
Creating a new extension from scratch covers the full scaffolding process, including manual setup without the kickstarter.
Step 2: Create the domain model
Add a class extending
\TYPO3\ to
Classes/. Properties map to database columns by name.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Conference extends AbstractEntity
{
protected string $title = '';
protected string $description = '';
protected ?\DateTimeImmutable $conferenceDate = null;
public function getTitle(): string
{
return $this->title;
}
public function getDescription(): string
{
return $this->description;
}
public function getEventDate(): ?\DateTimeImmutable
{
return $this->conferenceDate;
}
}
Key points:
- Declare properties
protected. Public properties also work and can keep the model shorter (no getters/setters), butprotectedkeeps the door open for getter/setter logic and makes lazy-loaded relations easier to reason about. Private properties are never populated by Extbase — useprotected, notprivate. - Do not initialise properties in the constructor. Extbase populates them directly when loading objects from the database, bypassing the constructor.
- Use typed properties. Extbase reads the type declarations to map values correctly.
See also
Step 3: Create the repository
For a basic repository, extending
\TYPO3\ is all you need.
The naming convention is mandatory: a model named
Conference must have a
repository named
Conference in the
\Domain\
namespace.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Domain\Repository;
class ConferenceRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {}
The base class provides
find,
find,
find, and
find out of the
box.
See also
Step 4: Define the database table (TCA)
Create Configuration/TCA/tx_myextension_domain_model_conference.php with the
column definitions matching your model properties.
Since TYPO3 v13, database columns are
auto-created from TCA definitions
— you no longer need to define every field in ext_tables.sql. Check the
database analyser after installation to confirm the generated schema matches your
expectations. If a column needs a non-default type or index, declare it explicitly
in ext_tables.sql and it will take precedence.
The TCA column names must match the property names of your model
(camelCase properties map to snake_case columns by default — for example
$conference maps to
conference_).
Tip
The kickstarter generates TCA and SQL for you if you define your model
properties during scaffolding. Use
vendor/ to
add a model to an existing extension.
See also
TCA Reference for the full TCA reference.
Step 5: Create the controller
Controllers live in Classes/ and extend
\TYPO3\. Each public method
ending in
Action is automatically available as a plugin action.
Use dependency injection to receive dependencies via the constructor. In Extbase, repositories and other services are injected this way — see also Injecting repositories with dependency injection.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Domain\Model\Conference;
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ConferenceController extends ActionController
{
public function __construct(
protected readonly ConferenceRepository $conferenceRepository,
) {}
public function listAction(): ResponseInterface
{
$this->view->assign('conferences', $this->conferenceRepository->findAll());
return $this->htmlResponse();
}
public function showAction(
Conference $conference,
): ResponseInterface {
$this->view->assign('conference', $conference);
return $this->htmlResponse();
}
}
- Assign variables to the view with
$this->view->assign.() - Return
$this->htmlto render the Fluid template.Response () - Typed action arguments are automatically resolved from the request —
passing a UID in the URL results in a fully populated
Conferenceobject in the action. Extbase loads it from the repository for you.
Step 6: Add Fluid templates
Create the template files Extbase expects by convention:
-
-
-
-
List.fluid.html
-
Show.fluid.html
-
-
-
-
Default.fluid.html
-
-
Partials/
-
The template name matches the action name — for example
list maps
to List.. Variables assigned in the controller are available
directly in the template.
<f:for each="{conferences}" as="conference">
<h2>{conference.title}</h2>
<p>{conference.conferenceDate -> f:format.date(format: 'd.m.Y')}</p>
<f:link.action action="show" arguments="{conference: conference}">
Read more
</f:link.action>
</f:for>
See also
Fluid — the full Fluid templating reference, including all built-in ViewHelpers.
Step 7: Register the plugin
Two calls are required — one in ext_localconf.php, one in
Configuration/TCA/Overrides/tt_content.php.
Since TYPO3 v14, plugins are registered as dedicated content types (
CType).
ext_localconf.php tells Extbase which controller actions the plugin
may call:
<?php
declare(strict_types=1);
use MyVendor\MyExtension\Controller\ConferenceController;
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
defined('TYPO3') or die();
ExtensionUtility::configurePlugin(
'MyExtension',
'ConferenceList',
[
ConferenceController::class => ['list', 'show'],
],
);
Configuration/TCA/Overrides/tt_content.php registers the plugin as a
content element in the backend:
<?php
declare(strict_types=1);
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
defined('TYPO3') or die();
ExtensionUtility::registerPlugin(
'MyExtension',
'ConferenceList',
'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:plugin.conferencelist.title',
'EXT:my_extension/Resources/Public/Icons/Extension.svg',
);
See also
Step 8: Configure routing (optional but recommended)
Without a route enhancer, URLs contain the raw plugin namespace parameters
(for example: ?tx_).
Add an Extbase route enhancer to your site configuration to get
clean URLs like /conferences/.
routeEnhancers:
ConferenceList:
type: Extbase
extension: MyExtension
plugin: ConferenceList
defaultController: 'Conference::list'
routes:
- routePath: '/conferences'
_controller: 'Conference::list'
- routePath: '/conferences/{conference}'
_controller: 'Conference::show'
aspects:
conference:
type: PersistedAliasMapper
tableName: tx_myextension_domain_model_conference
routeFieldName: slug
See also
Routing for Extbase plugins — the full routing chapter with detailed examples and common mistakes.
Step 9: Install and try it
Install your extension if it is not already active. In a Composer-based project, require it first:
composer require myvendor/my-extension
In non-composer-based projects, the extension is already in place, as you develop it within the code base. In both cases, you need to activate it:
vendor/bin/typo3 extension:activate my_extension
Then in the TYPO3 backend:
- Create a sysfolder page and add your conference records there.
- Create or edit a regular page, add a content element, and select your plugin from the content element type list.
- Set the Record Storage Page on the plugin content element to
the sysfolder from step 1 (or configure
plugin.in TypoScript).tx_ myextension. persistence. storage Pid - Open the page in the frontend — you should see your list view.
If the list is empty, check the storagePid first. See storagePid — when findAll() returns nothing.
What next?
You have a working extension. From here:
- Core concepts of Extbase — understand the MVC and ORM patterns underlying everything above.
- Querying the database with Extbase — write custom repository queries with ordering, filtering, and limits.
- Validation in Extbase — validate model properties and action arguments automatically.
- Caching for Extbase plugins — understand how caching works for your plugin and what your responsibilities are.