Property types of Extbase models

In Extbase models, property types can be defined either through a native PHP type declaration or a @var annotation for untyped properties.

For persisted properties, it is important that the PHP property type and the matching TCA field configuration are compatible — see the list below for commonly used property types and their mappings.

Primitive types in Extbase properties

The following table shows the primitive PHP types that are commonly used in Extbase models and the TCA field types they typically map to:

PHP Type (section) Common TCA field types Database column types
string input, text, email, password, color, select, passthrough varchar(255), text
int number (with format: integer), select with numeric values int(11), tinyint(1)
float number (with format: decimal) double, float
bool check tinyint(1)

If the primitive PHP type is nullable (?string, ?int ... ) the TCA field must also be nullable. A checkbox will appear in the backend, which deactivates the field by default. If the field is deactivated it is saved as NULL in the database.

string properties in Extbase

Extbase properties of the built-in primitive type string are commonly used with TCA fields of type Input (max 255 chars) or Text areas & RTE.

Strings can also be used for Select fields that set a single value where the values are strings, for Color and Email field types and Pass through / virtual fields.

packages/my_extension/Classes/Domain/Model/StringExample.php
<?php

declare(strict_types=1);

namespace Vendor\Extension\Domain\Model;

use TYPO3\CMS\Extbase\Annotation\Validate;

class StringExample extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
    #[Validate(['validator' => 'StringLength', 'options' => ['maximum' => 255]])]
    protected string $title = '';
    public ?string $subtitle = null;
    protected string $description = '';
    protected string $icon = 'fa-solid fa-star';
    #[Validate(['validator' => 'MyColorValidator'])]
    protected string $color = '#ffffff';
    #[Validate(['validator' => 'EmailAddress'])]
    protected string $email = '';
    protected string $passwordHash = '';
    #[Validate(['validator' => 'StringLength', 'options' => ['maximum' => 255]])]
    protected string $virtualValue = '';
}
Copied!
packages/my_extension/Configuration/TCA/tx_myextension_domain_model_stringexample.php
<?php

return [
    // ...
    'columns' => [
        'title' => [
            'label' => 'Title',
            'config' => [
                'type' => 'input',
                'size' => 50,
                'eval' => 'trim,required',
            ],
        ],
        'subtitle' => [
            'label' => 'Subtitle',
            'config' => [
                'type' => 'input',
                'eval' => 'trim',
                'nullable' => true,
            ],
        ],
        'description' => [
            'label' => 'Description',
            'config' => [
                'type' => 'text',
                'cols' => 40,
                'rows' => 5,
            ],
        ],
        'icon' => [
            'label' => 'Icon',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'maxitems' => 1,
                'items' => [
                    [
                        'label' => 'Font Awesome Icons',
                        'value' => '--div--',
                    ],
                    [
                        'label' => 'Star',
                        'value' => 'fa-solid fa-star',
                    ],
                    [
                        'label' => 'Heart',
                        'value' => 'fa-solid fa-heart',
                    ],
                    [
                        'label' => 'Comment',
                        'value' => 'fa-solid fa-comment',
                    ],
                ],
                'default' => 'fa-solid fa-star',
            ],
        ],
        'color' => [
            'label' => 'Color',
            'config' => [
                'type' => 'color',
            ],
        ],
        'email' => [
            'label' => 'Email',
            'config' => [
                'type' => 'email',
            ],
        ],
        'password' => [
            'label' => 'Password',
            'config' => [
                'type' => 'password',
            ],
        ],
        'virtualValue' => [
            'label' => 'Virtual Value',
            'config' => [
                'type' => 'passthrough',
            ],
        ],
    ],
];
Copied!
packages/my_extension/ext_tables.sql
CREATE TABLE tx_myextension_domain_model_stringexample (
     virtual_value varchar(255) DEFAULT '' NOT NULL,
);
Copied!

If fields are editable by frontend users, you should use Validators to prohibit values being input that are not allowed by their corresponding TCA fields / database columns. For virtual fields (passthrough), you must manually define the database schema in ext_tables.sql.

When using a nullable primitive type ( ?string) in your Extbase model, you must set the field to nullable in the TCA by setting nullable to true.

int properties in Extbase

Extbase properties of the built-in primitive type int are commonly used with TCA fields of type Number (with format integer) and Select fields that store integer values — for example, simple option fields where the value is a numeric key (with no relation to an enum or database record).

These are typically used for ratings, importance levels, custom statuses, or small, fixed sets of choices.

packages/my_extension/Classes/Domain/Model/IntExample.php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class IntExample extends AbstractEntity
{
    #[Validate([
        'validator' => 'NumberRange',
        'options' => ['minimum' => 0, 'maximum' => 10],
    ])]
    public int $importance = 0;

    #[Validate([
        'validator' => 'NumberRange',
        'options' => ['minimum' => 0, 'maximum' => 3],
    ])]
    public int $status = 0;
}
Copied!
packages/my_extension/Configuration/TCA/tx_myextension_domain_model_intexample.php
<?php

return [
    'columns' => [
        // ...

        'importance' => [
            'label' => 'Importance',
            'config' => [
                'type' => 'number',
                'default' => 0,
            ],
        ],

        'status' => [
            'label' => 'Status',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'items' => [
                    ['label' => 'None', 'value' => 0],
                    ['label' => 'Low', 'value' => 1],
                    ['label' => 'Medium', 'value' => 2],
                    ['label' => 'High', 'value' => 3],
                ],
                'default' => 0,
            ],
        ],

        // ...
    ],
];
Copied!

When not to use type int for a property

The int type is commonly used for simple numeric values, but it should not be used in the following cases:

  • Date and time fields: For fields configured with datetime, use \DateTimeInterface instead of int to benefit from proper time handling and formatting in Extbase and Fluid.
  • Boolean values: For fields using check, use the bool type instead of int to reflect the binary nature (0/1) of the value more clearly. See bool properties.
  • Multi-value selections: If a field uses selectMultipleSideBySide or similar to store multiple selections, use array or ObjectStorage of related objects.
  • Enums: For fixed sets of numeric values, avoid using int and instead use an enum to ensure type safety and better readability in your model and templates. See Enumerations.
  • Relations to other database tables: Fields representing foreign keys (for example select with foreign_table, IRRE / inline, or group) should not be type int, but rather ObjectStorage <YourModel> or ?YourModel depending on whether the relation is singular or plural. See Relations between Extbase models.

float properties in Extbase

Properties of built-in primitive type float (also known as double) are used to store decimal values such as prices, ratings, weights, or coordinates.

In TYPO3 v13, these are typically used with the Number TCA field type. To accept and display decimal numbers in the backend form, the format option must be set to decimal.

packages/my_extension/Classes/Domain/Model/FloatExample.php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class FloatExample extends AbstractEntity
{
    public float $price = 0.0;

    public ?float $rating = null;
}
Copied!
packages/my_extension/Configuration/TCA/tx_myextension_domain_model_floatexample.php
<?php

return [
    // ...
    'columns' => [
        'price' => [
            'label' => 'Price',
            'config' => [
                'type' => 'number',
                'format' => 'decimal',
                'default' => 0.0,
            ],
        ],
        'rating' => [
            'label' => 'Rating',
            'config' => [
                'type' => 'passtrough',
                'format' => 'decimal',
                'nullable' => true,
            ],
        ],
    ],
];
Copied!

bool properties in Extbase

Properties of built-in primitive type bool are used for binary decisions, such as opting in to a feature or accepting terms and conditions.

In TYPO3 v13, boolean values are typically managed using Check fields with renderType: checkboxToggle, which provides a user-friendly toggle UI.

packages/my_extension/Classes/Domain/Model/BoolExample.php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class BoolExample extends AbstractEntity
{
    public bool $wantsNewsletter = false;

    #[Validate([
        'validator' => 'Boolean',
        'options' => ['is' => true],
    ])]
    public bool $acceptedPrivacyPolicy = false;
}
Copied!
packages/my_extension/Configuration/TCA/tx_myextension_domain_model_boolexample.php
<?php

return [
    'columns' => [
        'wants_newsletter' => [
            'label' => 'Subscribe to newsletter',
            'config' => [
                'type' => 'check',
                'renderType' => 'checkboxToggle',
                'default' => 0,
            ],
        ],
        'accepted_privacy_policy' => [
            'label' => 'I accept the privacy policy',
            'config' => [
                'type' => 'check',
                'default' => 0,
            ],
        ],
    ],
];
Copied!

Predefined classes as types of models

Datetime model types

The PHP classes \DateTime and \DateTimeImmutable can be used with the TCA field type datetime

The value can be stored in the database as either a unix timestamp int(11) (default) or type datetime (TCA dbType datetime).

In the frontend they are commonly displayed using the f:format.date ViewHelper <f:format.date>.

packages/my_extension/Classes/Domain/Model/DateExample.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class DateExample extends AbstractEntity
{
    /**
     * A datetime stored in an integer field
     */
    public ?\DateTime $datetimeInt = null;

    /**
     * A datetime stored in a datetime field
     */
    public ?\DateTime $datetimeDatetime = null;
}
Copied!

Use \DateTimeImmutable if you want the date to be immutable.

packages/my_extension/Configuration/TCA/tx_myextension_domain_model_dateexample.php
<?php

declare(strict_types=1);

return [
    // ...
    'columns' => [
        'datetime_text' => [
            'exclude' => true,
            'label' => 'type=datetime, db=text',
            'config' => [
                'type' => 'datetime',
            ],
        ],
        'datetime_int' => [
            'exclude' => true,
            'label' => 'type=datetime, db=int',
            'config' => [
                'type' => 'datetime',
            ],
        ],
        'datetime_datetime' => [
            'exclude' => true,
            'label' => 'type=datetime, db=datetime',
            'config' => [
                'type' => 'datetime',
                'dbType' => 'datetime',
                'nullable' => true,
            ],
        ],
    ],
];
Copied!
packages/my_extension/Resources/Private/Templates/Date/Show.html
<f:format.date format="%d. %B %Y">{example.datetimeInt}</f:format.date>
<f:format.date format="%d. %B %Y">{example.datetimeDatetime}</f:format.date>

Or inline:

{example.datetimeInt -> f:format.date(format: '%d. %B %Y')}
{example.datetimeDatetime -> f:format.date(format: '%d. %B %Y')}
Copied!

Enumerations as Extbase model property

New in version 13.0

Native PHP enumerations can be used for properties where the database field has a set of values which can be represented by a backed enum. A property with an enum type should be used with a TCA field that only allows specific values to be stored in the database, for example Select fields and Radio buttons.

An enum can be used for a property in the model:

EXT:my_extension/Classes/Domain/Model/Paper.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use MyVendor\MyExtension\Enum\Status;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Paper extends AbstractEntity
{
    protected Status $status = Status::DRAFT;

    // ... more properties
}
Copied!

It is recommended to use backed enumerations:

EXT:my_extension/Classes/Enum/Status.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Enum;

enum Status: string
{
    case DRAFT = 'draft';
    case IN_REVIEW = 'in-review';
    case PUBLISHED = 'published';
    public const LLL_PREFIX = 'LLL:EXT:my_extension/Resources/Private/Languages/locallang.xlf:status-';
    public function getLabel(): string
    {
        return self::LLL_PREFIX . $this->value;
    }
}
Copied!

Implementing a method getLabel() enables you to use the same localization strings in both the Backend (see TCA) and the Frontend (see Fluid).

packages/my_extension/Configuration/TCA/tx_myextension_domain_model_paper.php
<?php

use MyVendor\MyExtension\Enum\Status;

return [
    // ...
    'columns' => [
        'status' => [
            'label' => 'Status',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'maxitems' => 1,
                'items' => [
                    [
                        'label' => Status::DRAFT->getLabel(),
                        'value' => Status::DRAFT->name,
                    ],
                    [
                        'label' => Status::IN_REVIEW->getLabel(),
                        'value' => Status::IN_REVIEW->name,
                    ],
                    [
                        'label' => Status::PUBLISHED->getLabel(),
                        'value' => Status::PUBLISHED->name,
                    ],
                ],
                'default' => Status::DRAFT->name,
            ],
        ],
    ],
];
Copied!

You can use the enums in TCA to display localized labels, for example.

packages/my_extension/Resources/Private/Templates/Paper/Show.html
<div class="status">
    <f:translate key="{paper.status.label}" default="Not Translated"/>
    [{paper.status.value}]
</div>
<f:variable name="draftStatus"><f:constant name="\MyVendor\MyExtension\Enum\Status::DRAFT"/></f:variable>
<f:switch expression="{paper.status.name}">
    <f:case value="{draftStatus.name}"></f:case>
</f:switch>
Copied!
packages/my_extension/Resources/Private/Languages/locallang.xlf
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.0" xmlns:t="x-gettext">
    <file source-language="en" datatype="plaintext" original="messages" date="2024-04-18T00:00:00Z">
        <header/>
        <body>
            <trans-unit id="status-draft">
                <source>Draft</source>
            </trans-unit>
            <trans-unit id="status-in-review">
                <source>In Review</source>
            </trans-unit>
            <trans-unit id="status-published">
                <source>Published</source>
            </trans-unit>
        </body>
    </file>
</xliff>
Copied!

An enum case can be used in Fluid by calling the enum built-in properties name and value or by using getters. Methods with a different naming scheme cannot be used directly in Fluid.

You can use the Constant ViewHelper <f:constant> to load a specific enum case into a variable to make comparisons or to create selectors.

Union types of Extbase model properties

New in version 12.3

Previously, whenever a union type was needed, union type declarations led Extbase not detecting any type at all, resulting in the property not being mapped. However, union types could be resolved via docblocks. Since TYPO3 v12.3 native PHP union types can be used.

Union types can be used in properties of an entity, for example:

EXT:my_extension/Classes/Domain/Model/Entity.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model\MyEntity;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;

class Entity extends AbstractEntity
{
    protected ChildEntity|LazyLoadingProxy $property;
}
Copied!

This is especially useful for lazy-loaded relations where the property type is ChildEntity|\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy.

There is something important to understand about how Extbase detects union types when it comes to property mapping, that is when a database row is mapped onto an object. In this situation Extbase needs to know the specific target type - no union, no intersection, just one type. In order to do the mapping, Extbase uses the first declared type as a primary type.

EXT:my_extension/Classes/Domain/Model/Entity.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model\MyEntity;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Entity extends AbstractEntity
{
    protected string|int $property;
}
Copied!

In the example above, string is the primary type. int|string would result in int as the primary type.

There is one important thing to note and one exception to this rule. First of all, null is not considered a type. null|string results in the primary type string which is nullable. null|string|int also results in the primary type string. In fact, null means that all other types are nullable. null|string|int boils down to ?string or ?int.

Secondly, LazyLoadingProxy is never detected as a primary type because it is just a proxy and, once loaded, not the actual target type.

EXT:my_extension/Classes/Domain/Model/Entity.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model\MyEntity;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;

class Entity extends AbstractEntity
{
    protected LazyLoadingProxy|ChildEntity $property;
}
Copied!

Extbase supports this and detects ChildEntity as the primary type, although LazyLoadingProxy is the first item in the list. However, it is recommended to place the actual type first for consistency reasons: ChildEntity|LazyLoadingProxy.

A final word on \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage : it is a subclass of \TYPO3\CMS\Extbase\Persistence\ObjectStorage , therefore the following code works and has always worked:

EXT:my_extension/Classes/Domain/Model/Entity.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model\MyEntity;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;

class Entity extends AbstractEntity
{
    /**
     * @var ObjectStorage<ChildEntity>
     * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
     */
    protected ObjectStorage $property;

    public function initializeObject(): void
    {
        $this->property = new ObjectStorage();
    }
}
Copied!