---
title: "Controller layer in Extbase"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-controller@main"
source: "ExtensionArchitecture/Extbase/Controller/Index.rst"
rendered: "2026-09-18T15:39:49+00:00"
---

# Controller layer in Extbase {#extbase-controller}

The controller layer sits between the request and the view. It handles the
request, coordinates repository and service calls, assigns variables for the
view, and returns a response. Business logic can live in the controller
directly — for anything complex or reused, a dedicated service class keeps the
controller focused. Direct database queries belong in repositories, not in
controllers.

**On this page**

-   [Responses are not tied to Fluid](https://docs.typo3.org/permalink/t3coreapi:responses-are-not-tied-to-fluid@main)
-   [Injecting dependencies into Extbase controllers](https://docs.typo3.org/permalink/t3coreapi:injecting-dependencies-into-extbase-controllers@main)
-   [In this chapter](https://docs.typo3.org/permalink/t3coreapi:in-this-chapter@main)

## Responses are not tied to Fluid {#extbase-controller-overview-responses}

Fluid is the recommended way to render output but this is not a
hard requirement. Every action must return a
`\Psr\Http\Message\ResponseInterface` — what goes into that response is
entirely up to the action.

Calling `$this->htmlResponse()` without arguments renders the Fluid
template and wraps it in a response. Passing a string to
`$this->htmlResponse()` skips Fluid and uses that string as the body
directly:

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

```php
// Render the Fluid template for the current action
return $this->htmlResponse();

// Return a hand-built HTML string without Fluid
return $this->htmlResponse('<p>Hello world</p>');

// Return JSON
return $this->jsonResponse(json_encode(['count' => 42]));

// Redirect to another action (returns a redirect response, not a body)
return $this->redirect('list');
```

All of these satisfy the PSR-7 `\Psr\Http\Message\ResponseInterface` contract.
Returning anything other than a `\Psr\Http\Message\ResponseInterface`
from an action throws a `\RuntimeException` at dispatch time.

## Injecting dependencies into Extbase controllers {#extbase-controller-overview-di}

Dependencies are added to controllers via
[dependency injection](https://docs.typo3.org/permalink/t3coreapi:dependency-injection@main). Two mechanisms are
available:

**Constructor injection** is the standard approach. Declare dependencies as
`protected readonly` constructor parameters and the
DI container will provide them automatically:

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ConferenceController extends ActionController
{
  public function __construct(
    protected readonly ConferenceRepository $conferenceRepository,
  ) {}
}

```

**Inject methods** should be used when a controller extends a parent
class that already has a constructor. As PHP only allows one
constructor per class, additional dependencies cannot be added without
repeating the parent's parameter list. A public method named
`inject` with the dependency name will receive the dependency instead.

A typical case is a child controller that extends a base controller from your
own extension or a third-party package. The base controller already owns the
constructor, so the child uses inject methods for its own dependencies:

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ConferenceController extends ActionController
{
  public function __construct(
    protected readonly ConferenceRepository $conferenceRepository,
  ) {}
}

```

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Service\ConferenceService;

class SpecialConferenceController extends ConferenceController
{
  protected ConferenceService $conferenceService;

  public function injectConferenceService(
    ConferenceService $conferenceService,
  ): void {
    $this->conferenceService = $conferenceService;
  }
}

```

Inject methods are a fully supported DI pattern, not a fallback. Constructor
injection is the recommended best practice — dependencies are declared in one
place and immediately visible to anyone reading the class. Inject methods are
the cleaner solution when a constructor is already owned by a parent class and
cannot be extended without repeating its full parameter list.

Both mechanisms can be combined in the same class. Injected properties should
be `protected`, not `private`, so subclasses can access them.

## In this chapter {#extbase-controller-overview-pages}

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

    Actions, action arguments and automatic object resolution, response
    helpers, redirect and forward, flash messages, per-action initializers,
    `#[Authorize]`, `#[RateLimit]`, and `errorAction`.

-   **[Property mapping: request arguments to objects](https://docs.typo3.org/permalink/t3coreapi:extbase-controller-propertymapping@main)**

    How raw request data is converted to typed PHP objects; the
    `__trustedProperties` mechanism; when manual allowlisting in
    `initialize*Action()` is necessary.

> [!NOTE]
> **See also**
>
> -   [Extbase validation](https://docs.typo3.org/permalink/t3coreapi:extbase-validation-overview@main)
>     for validation rules on action arguments and model properties; how
>     validation failure feeds into `errorAction`.
> -   [Extbase view layer](https://docs.typo3.org/permalink/t3coreapi:extbase-view-overview@main)
>     for Fluid integration, template resolution, and response types.
