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.
Property types in Extbase
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 , text |
int | number (with format: integer ), select with
numeric values | int , tinyint |
float | number (with format: decimal ) | double , float |
bool | check | tinyint |
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.
<?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 = '';
}
<?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',
],
],
],
];
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.
<?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;
}
<?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,
],
],
// ...
],
];
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
\Date
instead ofTime Interface int
to benefit from proper time handling and formatting in Extbase and Fluid. - Boolean values: For fields using
check, use the
bool
type instead ofint
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
orObject
of related objects.Storage - Enums: For fixed sets of numeric values, avoid using
int
and instead use anenum
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 ratherObject
orStorage <Your Model> ?Your
depending on whether the relation is singular or plural. See Relations between Extbase models.Model
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
.
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
render
, which provides a user-friendly toggle UI.
<?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;
}
<?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,
],
],
],
];
Predefined classes as types of models
Datetime model types
The PHP classes
\Date
and
\Date
can be used with
the TCA field type datetime
The value can be stored in the database as either a unix timestamp
int
(default) or type
datetime
(TCA dbType
datetime
).
In the frontend they are commonly displayed using the f:format.date ViewHelper <f:format.date>.
<?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;
}
Use
\Date
if you want the date to be
immutable.
<?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,
],
],
],
];
Enumerations as Extbase model property
New in version 13.0
Native support for backed enumerations has been introduced. It is no longer necessary to extend the deprecated TYPO3 Core class \TYPO3\CMS\Core\Type\Enumeration.
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:
It is recommended to use backed enumerations:
<?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;
}
}
Implementing a method get
enables you to use the same
localization strings in both the Backend (see TCA) and the Frontend (see Fluid).
<?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,
],
],
],
];
You can use the enums in TCA to display localized labels, for example.
<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>
<?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>
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:
<?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;
}
This is especially useful for lazy-loaded relations where the property type is
Child
.
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.
<?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;
}
In the example above,
string
is the primary type.
int
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
results in the
primary type
string
which is nullable.
null
also results in the primary type
string
. In fact,
null
means that all other types are nullable.
null
boils down to
?string
or
?int
.
Secondly,
Lazy
is never detected as a primary type because it
is just a proxy and, once loaded, not the actual target type.
<?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;
}
Extbase supports this and detects
Child
as the primary type,
although
Lazy
is the first item in the list. However, it is
recommended to place the actual type first for consistency reasons:
Child
.
A final word on
\TYPO3\
:
it is a subclass of
\TYPO3\
,
therefore the following code works and has always worked:
<?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();
}
}