---
title: "Extbase quick start for experienced developers"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-quickstart@main"
source: "ExtensionArchitecture/Extbase/QuickStart/Index.rst"
rendered: "2026-09-22T15:26:06+00:00"
---

# Extbase quick start for experienced developers {#extbase-quickstart}

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](https://docs.typo3.org/permalink/t3coreapi:step-1-scaffold-the-extension@main)
-   [Step 2: create the domain model](https://docs.typo3.org/permalink/t3coreapi:step-2-create-the-domain-model@main)
-   [Step 3: create the repository](https://docs.typo3.org/permalink/t3coreapi:step-3-create-the-repository@main)
-   [Step 4: define the database table (TCA)](https://docs.typo3.org/permalink/t3coreapi:step-4-define-the-database-table-tca@main)
-   [Step 5: create the controller](https://docs.typo3.org/permalink/t3coreapi:step-5-create-the-controller@main)
-   [Step 6: add Fluid templates](https://docs.typo3.org/permalink/t3coreapi:step-6-add-fluid-templates@main)
-   [Step 7: register the plugin](https://docs.typo3.org/permalink/t3coreapi:step-7-register-the-plugin@main)
-   [Step 8: configure routing (optional but recommended)](https://docs.typo3.org/permalink/t3coreapi:step-8-configure-routing-optional-but-recommended@main)
-   [Step 9: install and try it](https://docs.typo3.org/permalink/t3coreapi:step-9-install-and-try-it@main)
-   [What next?](https://docs.typo3.org/permalink/t3coreapi:what-next@main)

## Step 1: scaffold the extension {#extbase-quickstart-scaffold}

Use the [FriendsOfTYPO3 kickstarter package](https://github.com/FriendsOfTYPO3/kickstarter) to generate the extension skeleton:

**In your project root**

```bash
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.json` and the boilerplate files you need.
Since TYPO3 v14 you do not need `ext_emconf.php` unless you plan to publish
your extension to the [TYPO3 Extension Repository](https://extensions.typo3.org).

> [!NOTE]
> **See also**
>
> [Creating a new extension from scratch](https://docs.typo3.org/permalink/t3coreapi:extension-create-new@main) covers the full scaffolding process, including
> manual setup without the kickstarter.

## Step 2: create the domain model {#extbase-quickstart-model}

Add a class extending `\TYPO3\CMS\Extbase\DomainObject\AbstractEntity` to
`Classes/Domain/Model/`. Properties map to database columns by name.

**EXT:my_extension/Classes/Domain/Model/Conference.php**

```php
<?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), but `protected` keeps
    the door open for getter/setter logic and makes lazy-loaded relations easier
    to reason about. Private properties are never populated by Extbase — use
    `protected`, not `private`.
-   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.

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

## Step 3: create the repository {#extbase-quickstart-repository}

For a basic repository, extending
`\TYPO3\CMS\Extbase\Persistence\Repository` is all you need.
The naming convention is mandatory: a model named `Conference` must have a
repository named `ConferenceRepository` in the `\Domain\Repository`
namespace.

**EXT:my_extension/Classes/Domain/Repository/ConferenceRepository.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\Repository;

class ConferenceRepository extends Repository {}

```

The base class provides `findAll()`, `findByUid()`,
`findBy(array $criteria)`, and `findOneBy(array $criteria)` out of the
box.

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

## Step 4: define the database table (TCA) {#extbase-quickstart-tca}

Create [`Configuration/TCA/tx_myextension_domain_model_conference.php`](../../FileStructure/Configuration/TCA/Index.md#file-extension-configuration-tca-tablename-php) with the
column definitions matching your model properties.

Since TYPO3 v13, database columns are
[auto-created from TCA definitions](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/13.0/Feature-101553-Auto-createDBFieldsFromTCAColumns.html#feature-101553-1691166389)
— you no longer need to define every field in [`ext_tables.sql`](../../FileStructure/ExtTablesSql.md#file-extension-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`](../../FileStructure/ExtTablesSql.md#file-extension-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
`$conferenceDate` maps to `conference_date`).

> [!TIP]
> The kickstarter generates TCA and SQL for you if you define your model
> properties during scaffolding. Use `vendor/bin/typo3 make:model` to
> add a model to an existing extension.

> [!NOTE]
> **See also**
>
> [TCA Reference](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Index.html#start) for the full TCA reference.

## Step 5: create the controller {#extbase-quickstart-controller}

Controllers live in `Classes/Controller/` and extend
`\TYPO3\CMS\Extbase\Mvc\Controller\ActionController`. Each public method
ending in `Action` is automatically available as a plugin action.
Use [dependency injection](https://docs.typo3.org/permalink/t3coreapi:dependency-injection@main) to receive dependencies via the constructor. In Extbase, repositories and other services are injected this way — see also [Injecting repositories with dependency injection](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-repository-di@main).

**EXT:my_extension/Classes/Controller/ConferenceController.php**

```php
<?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->htmlResponse()` to render the Fluid template.
-   Typed action arguments are automatically resolved from the request —
    passing a UID in the URL results in a fully populated `Conference`
    object in the action. Extbase loads it from the repository for you.

> [!NOTE]
> **See also**
>
> [ActionController: actions, arguments and responses](https://docs.typo3.org/permalink/t3coreapi:extbase-controller-action@main)

## Step 6: add Fluid templates {#extbase-quickstart-templates}

Create the template files Extbase expects by convention:

-   EXT:my_extension/Resources/Private/
    -   Templates/
        -   Conference/
            -   List.fluid.html
            -   Show.fluid.html
    -   Layouts/
        -   Default.fluid.html
    -   Partials/

The template name matches the action name — for example `listAction()` maps
to `List.fluid.html`. Variables assigned in the controller are available
directly in the template.

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

```html
<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>
```

> [!NOTE]
> **See also**
>
> [View layer in Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-view-overview@main)
>
> [Fluid](https://docs.typo3.org/permalink/t3coreapi:fluid@main) — the full Fluid templating reference, including all built-in
> ViewHelpers.

## Step 7: register the plugin {#extbase-quickstart-plugin}

Two calls are required — one in [`ext_localconf.php`](../../FileStructure/ExtLocalconf.md#file-extension-ext-localconf-php), one in
[`Configuration/TCA/Overrides/tt_content.php`](../../FileStructure/Configuration/TCA/Index.md#file-extension-configuration-tca-overridessomefile-php).

Since TYPO3 v14, plugins are registered as dedicated content types (`CType`).

[`ext_localconf.php`](../../FileStructure/ExtLocalconf.md#file-extension-ext-localconf-php) tells Extbase which controller actions the plugin
may call:

**EXT:my_extension/ext_localconf.php**

```php
<?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`](../../FileStructure/Configuration/TCA/Index.md#file-extension-configuration-tca-overridessomefile-php) registers the plugin as a
content element in the backend:

**EXT:my_extension/Configuration/TCA/Overrides/tt_content.php**

```php
<?php

declare(strict_types=1);

use TYPO3\CMS\Extbase\Utility\ExtensionUtility;

defined('TYPO3') or die();

ExtensionUtility::registerPlugin(
  'MyExtension',
  'ConferenceList',
  'my_extension.messages:plugin.conferencelist.title',
  'content-plugin',
);

```

The icon argument passed to `registerPlugin()` must be a
[registered icon identifier](https://docs.typo3.org/permalink/t3coreapi:icon-registration@main), not a raw
`EXT:` path — the example above uses TYPO3's built-in default
plugin icon (`content-plugin`).

Registering a custom icon is optional. If you want one, register it in
[`Configuration/Icons.php`](../../FileStructure/Configuration/Icons.md#file-extension-configuration-icons-php):

**EXT:my_extension/Configuration/Icons.php**

```php
<?php

declare(strict_types=1);

use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;

return [
  'my-extension-conference-list' => [
    'provider' => SvgIconProvider::class,
    'source' => 'EXT:my_extension/Resources/Public/Icons/Extension.svg',
  ],
];

```

and reference its identifier (`my-extension-conference-list`)
instead of `content-plugin` in the `registerPlugin()` call above.

> [!NOTE]
> **See also**
>
> [Registering an Extbase frontend plugin](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-frontend-plugin@main)

## Step 8: configure routing (optional but recommended) {#extbase-quickstart-routing}

Without a route enhancer, URLs contain the raw plugin namespace parameters
(for example: `?tx_myextension_conferencelist[action]=show&tx_myextension_conferencelist[conference]=5`).
Add an Extbase route enhancer to your site configuration to get
clean URLs like `/conferences/my-conference`.

**config/sites/my-site/config.yaml (excerpt)**

```yaml
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

```

> [!NOTE]
> **See also**
>
> [Routing for Extbase plugins](https://docs.typo3.org/permalink/t3coreapi:extbase-routing@main) — the full routing chapter with detailed
> examples and common mistakes.

## Step 9: install and try it {#extbase-quickstart-install}

Install your extension if it is not already active. In a Composer-based project,
require it first:

**In your project root**

```bash
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:

**In your project root**

```bash
vendor/bin/typo3 extension:activate my_extension
```

Then in the TYPO3 backend:

1.  Create a sysfolder page and add your conference records there.
1.  Create or edit a regular page, add a content element, and select your
    plugin from the content element type list.
1.  Set the **Record Storage Page** on the plugin content element to
    the sysfolder from step 1 (or configure
    `plugin.tx_myextension.persistence.storagePid` in TypoScript).
1.  Open the page in the frontend — you should see your list view.

If the list is empty, check the storagePid first. See
[The storagePid constraint on repository queries](https://docs.typo3.org/permalink/t3coreapi:extbase-domain-repository-storagepid@main).

## What next? {#extbase-quickstart-next}

You have a working extension. From here:

-   [Core concepts of Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-concepts@main) — understand the MVC and ORM patterns
    underlying everything above.
-   [Querying the database with Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-persistence-queries@main) — write custom repository
    queries with ordering, filtering, and limits.
-   [Validation in Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-validation-overview@main) — validate model properties and action
    arguments automatically.
-   [Caching for Extbase plugins](https://docs.typo3.org/permalink/t3coreapi:extbase-caching-overview@main) — understand how caching works for your
    plugin and what your responsibilities are.
