---
title: "Data transfer objects (DTO) in Extbase"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-dto@main"
source: "ExtensionArchitecture/Extbase/Domain/Dto.rst"
rendered: "2026-09-21T11:24:09+00:00"
---

# Data transfer objects (DTO) in Extbase {#extbase-dto}

> [!WARNING]
> The information on this page is outdated!

A data transfer object (DTO) is a special kind of model used to transport
data between the business logic and the (Fluid) view but is not persisted
to the database.

Data objects can be validated and passed as parameters to controller actions,
just like persistable models.

A data object is defined by its values and cannot be referenced by an ID.

Forms are commonly bound to data objects. In multi-step forms, you can use
multiple data objects with distinct validation rules for each step.

> [!NOTE]
> **See also**
>
> -   [Data Transfer Objects (DTO) as a software design principle](https://docs.typo3.org/permalink/t3coreapi:concept-dto@main)
> -   [usetypo3.com: Data Transfer Objects in Extbase](https://usetypo3.com/dtos-in-extbase/)

## Example: a BMI calculator without storage {#extbase-dto-example-dto}

The body mass index (BMI) calculator in this example contains a form where users can
enter their height (in meters) and weight, and then calculate their BMI.
The measurements need to be validated but do not have to be stored in the database.

We define an object to contain and transport the measurements required for our
calculation:

**EXT:bmi_calculator/Classes/Domain/Model/Dto/MeasurementsDto.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Domain\Model\Dto;

final class MeasurementsDto
{
  private float $height;
  private int $weight;

  public function __construct(?float $height = 0, ?int $weight = 0)
  {
    $this->height = $height ?? 0;
    $this->weight = $weight ?? 0;
  }

  // getters and setters
}

```

If a constructor is defined, it will be called automatically; otherwise,
the setter methods will be invoked.

> [!NOTE]
> **See also**
>
> You can find the complete example on GitHub:
> [https://github.com/TYPO3-Documentation/bmi_calculator](https://github.com/TYPO3-Documentation/bmi_calculator)

## Using a DTO in the controller {#extbase-dto-example-dto-usage}

You can use the DTO to pass default data to the form and to receive the form
input:

**EXT:bmi_calculator/Classes/Controller/CalculatorController.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Controller;

use Psr\Http\Message\ResponseInterface;
use T3docs\BmiCalculator\Domain\Model\Dto\MeasurementsDto;
use T3docs\BmiCalculator\Service\BmiCalculatorService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CalculatorController extends ActionController
{
  public function __construct(
    private readonly BmiCalculatorService $bmiCalculatorService,
  ) {}

  public function formAction(): ResponseInterface
  {
    $defaultValues = new MeasurementsDto(1.73, 73);
    $this->view->assign('defaultValues', $defaultValues);
    return $this->htmlResponse();
  }
  public function resultAction(MeasurementsDto $measurements): ResponseInterface
  {
    $this->view->assign('measurements', $measurements);
    $this->view->assign(
      'result',
      $this->bmiCalculatorService->calculate($measurements),
    );
    return $this->htmlResponse();
  }
}

```

The DTO can also be used to transfer data to and from the business logic.

## DTO and validation {#extbase-dto-example-dto-validation}

All Extbase validators can be applied to a DTO as well as to an entity model.
The same `#[Validate(...)]` attribute can be used:

**EXT:bmi_calculator/Classes/Domain/Model/Dto/MeasurementsDto.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Domain\Model\Dto;

use TYPO3\CMS\Extbase\Attribute\Validate;

final class MeasurementsDto
{
  // Ensure that the height is in meters, not centimeters
  #[Validate(validator: 'NotEmpty')]
  #[Validate(
    validator: 'NumberRange',
    options: ['minimum' => 0.5, 'maximum' => 2.5],
  )]
  private float $height;

  // Weight must not be empty
  #[Validate(validator: 'NotEmpty')]
  private int $weight;

  public function __construct(?float $height, ?int $weight)
  {
    $this->height = $height ?? 0;
    $this->weight = $weight ?? 0;
  }

  // getters and setters
}

```

When an object is passed as an input parameter to a controller action that contains
invalid data (for example, a height given in centimeters and therefore larger than 2.5),
the [Error action](https://docs.typo3.org/permalink/t3coreapi:extbase-controller-action-error@main)
is called. If you have not overridden or changed the error action, it will display
a flash message and attempt to return the user to the previous action.

You can override the default (and sometimes cryptic) error message by implementing
the following method in your controller:

**EXT:bmi_calculator/Classes/Controller/CalculatorController.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CalculatorController extends ActionController
{
  // ...
  protected function getErrorFlashMessage(): bool|string
  {
    return 'Check your measurements. ';
  }
}

```

It is also possible to override the default error action `errorAction()`.

## Converting DTOs to domain models {#extbase-dto-example-dto-domain-model}

In order to be persisted to the database a DTO has to be transferred into a
domain model.

This can be achieved by implementing a `static` method converting the
DTO into a domain model:

To stay in our example above, let us assume we want to save the measurement
input of our users into the database in order to use it for statistical
purposes.

To avoid storing a float we will convert the height into centimeters.

As the static method is located in the same class it can access the protected
properties of the model directly.

**EXT:bmi_calculator/Classes/Domain/ModelMeasurements.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Domain\Model;

use T3docs\BmiCalculator\Domain\Model\Dto\MeasurementsDto;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

final class Measurements extends AbstractEntity
{
  protected int $height = 0;
  protected int $weight = 0;

  public static function fromMeasurementsDto(MeasurementsDto $measurementsDto): self
  {
    $model = new self();
    $model->weight = $measurementsDto->getWeight();
    $model->height = (int)($measurementsDto->getHeight() * 100);
    return $model;
  }
  // Getters and Setters
}

```

You can now transfer your data object into a model entity that can be saved into
the database:

**EXT:bmi_calculator/Classes/Controller/CalculatorController.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Controller;

use Psr\Http\Message\ResponseInterface;
use T3docs\BmiCalculator\Domain\Model\Dto\MeasurementsDto;
use T3docs\BmiCalculator\Domain\Model\Measurements;
use T3docs\BmiCalculator\Domain\Repository\MeasurementsRepository;
use T3docs\BmiCalculator\Service\BmiCalculatorService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CalculatorController extends ActionController
{
  public function __construct(
    private readonly BmiCalculatorService $bmiCalculatorService,
    private readonly MeasurementsRepository $measurementsRepository,
  ) {}
  public function resultAction(MeasurementsDto $measurements): ResponseInterface
  {
    $this->view->assign('measurements', $measurements);
    $this->view->assign(
      'result',
      $this->bmiCalculatorService->calculate($measurements),
    );
    $this->measurementsRepository->add(Measurements::fromMeasurementsDto($measurements));
    return $this->htmlResponse();
  }
  // ...
}

```

## Storing DTOs in the user session {#extbase-dto-example-dto-session}

DTOs are volatile by nature. The moment you leave the controller action they are
gone.

Let us assume we want to remember the measurements of the user as long as they
don't close their browser window.

We can now store the DTO into the user session (see
[Session data](https://docs.typo3.org/permalink/t3coreapi:session-data@main))
even if no frontend user is logged in.

In order to store the DTO into the session we need to serialize it, turn it
into a string. In order to load the DTO from the session we need to deserialize
it, turn it from a string back into a DTO.

**EXT:bmi_calculator/Classes/Domain/Model/Dto/Measurements.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Domain\Model\Dto;

final class MeasurementsDto
{
  private float $height;
  private int $weight;

  public function __construct(?float $height = 0, ?int $weight = 0)
  {
    $this->height = $height ?? 0;
    $this->weight = $weight ?? 0;
  }

  public function serialize(): string
  {
    return json_encode(
      [
        'height' => $this->height,
        'weight' => $this->weight,
      ],
    );
  }

  public static function deserialize(string $sessionData): self
  {
    $data = json_decode($sessionData);
    if (!is_object($data) || !isset($data->height) || !isset($data->weight)) {
      print_r($data);
      throw new \RuntimeException('Deserialization failed ');
    }
    return new self(
      height: (float)$data->height,
      weight: (int)$data->weight,
    );
  }

  // Getters and setters
}

```

We can now load the measurement from the session and store changes there. In
order to have the functionality encapsulated we implement a service for it:

**EXT:bmi_calculator/Classes/Service/UserSessionService.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\BmiCalculator\Service;

use T3docs\BmiCalculator\Domain\Model\Dto\MeasurementsDto;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;

class UserSessionService
{
  public const SESSION_KEY = 'tx_bmi_calculator_session';
  public function storeIntoSession(RequestInterface $request, MeasurementsDto $measurementsDto): void
  {
    $user = $this->getFrontendUser($request);
    // We use type ses to store the data in the session
    $user->setKey(
      'ses',
      self::SESSION_KEY,
      $measurementsDto->serialize(),
    );
    // Important: store session data! Or it is not available in the next request!
    $this->getFrontendUser($request)->storeSessionData();
  }

  public function getFromSession(RequestInterface $request): ?MeasurementsDto
  {
    $user = $this->getFrontendUser($request);
    $data = $user->getKey('ses', self::SESSION_KEY);
    if (!is_string($data)) {
      return null;
    }
    return MeasurementsDto::deserialize($data);
  }

  private function getFrontendUser(RequestInterface $request): FrontendUserAuthentication
  {
    // This will create an anonymous frontend user if none is logged in
    return $request->getAttribute('frontend.user');
  }
}

```

We can then inject the Service into out controller and load and store the
session data there:

**EXT:bmi_calculator/Classes/Controller/CalculatorController.php**

```php
<?php

declare(strict_types=1);

/*
 * This file is part of the package t3docs/bmi-calculator.
 *
 * For the full copyright and license information, please read the
 * LICENSE file that was distributed with this source code.
 */

namespace T3docs\BmiCalculator\Controller;

use Psr\Http\Message\ResponseInterface;
use T3docs\BmiCalculator\Domain\Model\Dto\MeasurementsDto;
use T3docs\BmiCalculator\Service\UserSessionService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class CalculatorController extends ActionController
{
  public function __construct(
    private readonly UserSessionService $userSessionService,
  ) {}

  public function formAction(): ResponseInterface
  {
    $defaultValues = $this->userSessionService->loadFromSession($this->request)
        ?? new MeasurementsDto(1.73, 73);
    $this->view->assign('defaultValues', $defaultValues);
    return $this->htmlResponse();
  }
  public function resultAction(MeasurementsDto $measurements): ResponseInterface
  {
    $this->userSessionService->storeIntoSession($this->request, $measurements);
    // ...
    return $this->htmlResponse();
  }
}

```

## Using DTOs as demand objects {#extbase-dto-example-dto-demand}

DTOs can also be used to handle instructional data. Let us assume we want
to display the measurements of previous users in a table. This table can be
sorted by weight or height and it can be filtered by different criteria.

We can use a DTO to transfer the current settings for the table and use this
DTO for filtering and sorting in the repository.
