---
title: "View layer in Extbase"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-view@main"
source: "ExtensionArchitecture/Extbase/View/Index.rst"
modified: "2026-09-14T11:10:56+00:00"
---

# View layer in Extbase

The view layer converts variables into rendered output such as strings of
HTML or JSON. It knows nothing about HTTP responses; that is
the controller's concern. The controller assigns variables and calls the render
method indirectly via `$this->htmlResponse()` or `$this->jsonResponse()`.

[Fluid](https://docs.typo3.org/permalink/t3coreapi:fluid@main) is the
recommended template engine. It integrates with Extbase out of the box and
does not require any additional setup. Other template engines such as Blade or Twig can
be integrated via
`ViewFactoryInterface`, but that is outside the
scope of this documentation.

**On this page**

-   [Assigning variables to the Extbase view](https://docs.typo3.org/permalink/t3coreapi:assigning-variables-to-the-extbase-view@main)
-   [How Fluid accesses object properties and collections](https://docs.typo3.org/permalink/t3coreapi:how-fluid-accesses-object-properties-and-collections@main)
-   [Fluid template file resolution in Extbase](https://docs.typo3.org/permalink/t3coreapi:fluid-template-file-resolution-in-extbase@main)
-   [Overriding Fluid template paths via TypoScript](https://docs.typo3.org/permalink/t3coreapi:overriding-fluid-template-paths-via-typoscript@main)
-   [Overriding Fluid templates from a third-party extension](https://docs.typo3.org/permalink/t3coreapi:overriding-fluid-templates-from-a-third-party-extension@main)
-   [ViewHelpers in Fluid templates](https://docs.typo3.org/permalink/t3coreapi:viewhelpers-in-fluid-templates@main)
-   [JsonView: rendering JSON responses from Extbase](https://docs.typo3.org/permalink/t3coreapi:jsonview-rendering-json-responses-from-extbase@main)

## Assigning variables to the Extbase view

Use `$this->view->assign()` to make a value available under the variable name in the
template. `assignMultiple()` assigns several values at once:

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

```php
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());
        $this->view->assign('title', 'Upcoming conferences');
        return $this->htmlResponse();
    }

    public function showAction(Conference $conference): ResponseInterface
    {
        $this->view->assignMultiple([
            'conference' => $conference,
            'speakers' => $conference->getSpeakers(),
        ]);
        return $this->htmlResponse();
    }
}
```

The name passed to `assign()` becomes the variable name in the template, for example
`{conferences}`, `{title}`, and `{conference}` above.

`$this->settings` is the `settings` slice of the full Extbase
configuration array. How that array is assembled — and how `view`,
`persistence`, and `settings` relate to TypoScript paths
and FlexForm overrides — is covered in the registration chapters:
[How the plugin configuration is assembled](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-frontend-plugin-configuration-assembly@main) and
[How the module configuration is assembled](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-backend-module-configuration-assembly@main).

### Assigning variables needed in every action

If a variable must be available in every action of a controller — for example
the current site object or a global configuration value — override
`initializeAction()` and assign it once there instead of repeating the
call in each action method:

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

```php
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

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

    #[\Override]
    protected function initializeAction(): void
    {
        /** @var Site $site */
        $site = $this->request->getAttribute('site');
        $this->view->assign('siteSettings', $site->getSettings()->all());
    }

    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();
    }
}
```

If a variable is only needed in one or two actions, assign it directly inside
those action methods — `initializeAction()` is not required.

## How Fluid accesses object properties and collections

If a template variable is an object, Fluid resolves property paths such as
`{conference.title}` in the following order:

-   A public property `$title` directly.
-   A public getter method `getTitle()`.
-   A public method `hasTitle()`.
-   A public method `isTitle()`.

This means `protected` properties with conventional getters work
transparently in templates — `{conference.title}` calls
`getTitle()`.

> [!WARNING]
> If none of the above exist, Fluid renders an empty string without an error or
> exception. A typo in a property name, a missing getter, or a `private`
> property (which is never accessible to Fluid) will produce silent blank
> output. See
> [Template variable renders empty](https://docs.typo3.org/permalink/t3coreapi:extbase-appendix-pitfalls-template-empty@main)
> in the common pitfalls list.

Property paths can be chained: `{conference.mainSpeaker.name}` resolves
`getMainSpeaker()` on the conference and then `getName()` on the
result.

**Arrays and ObjectStorage collections** are accessed using
[f:for](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/For.html#typo3fluid-fluid-for) to
iterate, or with an explicit numeric index:

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

```html
<!-- Iterate over a QueryResult, ObjectStorage, or plain PHP array -->
<f:for each="{conferences}" as="conference">
    <h2>{conference.title}</h2>
</f:for>

<!-- Access a specific index (zero-based) -->
<p>{conferences.0.title}</p>

<!-- Access a named key of an associative array -->
<p>{data.headline}</p>
```

`\TYPO3\CMS\Extbase\Persistence\ObjectStorage` implements the PHP
[Traversable](https://www.php.net/manual/en/class.traversable.php) interface,
so `f:for` works with it just like with arrays.

## Fluid template file resolution in Extbase

Extbase finds a template file by searching through three configurable path lists:
`templateRootPaths`, `layoutRootPaths`, and `partialRootPaths`.
Each list is a numerically keyed array; Fluid searches from the highest key
downward and uses the first match it finds.

**Default paths** are always appended at key `0` if not already present:

-   `EXT:my_extension/Resources/Private/Templates/`
-   `EXT:my_extension/Resources/Private/Layouts/`
-   `EXT:my_extension/Resources/Private/Partials/`

**Template file naming convention:** the file must exist at
`Templates/{ControllerName}/{ActionName}.fluid.html`. The controller
name is the class name without the `Controller` suffix —
`ConferenceController` → `Conference/`. The action name matches the
method name without the `Action` suffix, with the first letter
uppercased — `listAction()` → `List.fluid.html`.

> [!NOTE]
> File names are case-sensitive on Linux systems, therefore
> `list.fluid.html` and `List.fluid.html` are different files.
> The convention requires an uppercase first letter.

**Bypassing the naming convention** is possible but discouraged. Passing an
explicit template path to `$this->view->render()` overrides automatic
resolution:

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

```php
public function listAction(): ResponseInterface
{
    $this->view->assign('conferences', $this->conferenceRepository->findAll());
    return $this->htmlResponse(
        $this->view->render('Conference/CustomList')
    );
}
```

This bypasses the controller/action convention. Reserve it for rare
situations where one action needs to render a different template, for example
for AJAX variants that share a controller action.

## Overriding Fluid template paths via TypoScript

Add paths via TypoScript to extend or override the default resolution. Use the
plugin-specific path `plugin.tx_myextension_myplugin` to target one
plugin, or `plugin.tx_myextension` (no plugin suffix) to set
defaults for every plugin of the extension:

**EXT:my_extension/Configuration/Sets/MyExtension/setup.typoscript**

```typoscript
plugin.tx_myextension_conferencelist.view {
    templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates/
    layoutRootPaths.10 = EXT:my_extension/Resources/Private/Layouts/
    partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/
}
```

Fluid searches from the highest key downward, so the path at key
`10` above takes precedence over a default at key `0`.

**Finding the TypoScript object path for a plugin:** open the TYPO3 backend,
navigate to the site or page containing the plugin, and open
**Site Management > TypoScript**. The Active TypoScript module shows
the computed TypoScript tree including all registered plugin objects. See
[How the plugin configuration is assembled](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-frontend-plugin-configuration-assembly@main) for how the
extension-wide and plugin-specific paths are merged.

## Overriding Fluid templates from a third-party extension

To replace a template provided by an extension you do not control, add the
override paths to your own extension or site package. Never modify a
third-party extension directly. Add a path at a key higher than the one used in the
original extension. Most extensions register their paths at key
`10` or leave the default at key `0`. Using key
`20` in your sitepackage is therefore safe in the majority of
cases:

**EXT:my_sitepackage/Configuration/Sets/MySitepackage/setup.typoscript**

```typoscript
plugin.tx_thirdpartyextension.view {
    templateRootPaths.20 = EXT:my_sitepackage/Resources/Private/ThirdParty/Templates/
    partialRootPaths.20 = EXT:my_sitepackage/Resources/Private/ThirdParty/Partials/
}
```

Place your overriding template in the same relative path as in the original.
`Conference/List.fluid.html` overrides `Conference/List.fluid.html`
in the third-party extension.

**Key value is the only thing that matters:** TypoScript path arrays are
assembled from all active extensions and resolved by numeric key — the higher
key wins. Load order between extensions does not affect this. Declare the
third-party extension as a dependency in `composer.json` to ensure it is
installed, but not because load order influences template resolution:

> [!NOTE]
> **See also**
>
> -   [Template file not found, or wrong template rendered](https://docs.typo3.org/permalink/t3coreapi:extbase-appendix-pitfalls-template-not-found@main)
>     for common causes and how to debug them.

## ViewHelpers in Fluid templates

[ViewHelpers](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Index.html#start)
are Fluid template functions and tags. They are used for
conditionals, loops, links, formatting, and much more. The built-in ViewHelpers
use the `f:` namespace, which is available in every template without
declaration.

Two equivalent syntaxes exist — tag style and inline style:

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

```html
<!-- Tag style -->
<f:for each="{conferences}" as="conference">
    <f:if condition="{conference.published}">
        <h2>{conference.title}</h2>
        <p><f:format.date format="d.m.Y">{conference.conferenceDate}</f:format.date></p>
        <f:link.action action="show" arguments="{conference: conference}">
            Details
        </f:link.action>
    </f:if>
</f:for>

<!-- Inline style (useful inside HTML attributes) -->
<a href="{f:uri.action(action: 'show', arguments: '{conference: conference}')}">
    Details
</a>
<time datetime="{conference.conferenceDate -> f:format.date(format: 'Y-m-d')}">
    {conference.conferenceDate -> f:format.date(format: 'd.m.Y')}
</time>
```

Commonly used ViewHelpers (with links to their full reference) are:

-   [f:for](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/For.html#typo3fluid-fluid-for)
    — iterate over arrays and `\TYPO3\CMS\Extbase\Persistence\ObjectStorage`
    collections.
-   [f:if](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/If.html#typo3fluid-fluid-if)
    — conditional output.
-   [f:link.action](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Link/Action.html#typo3-fluid-link-action)
    — generate links to controller actions.
-   [f:uri.action](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Uri/Action.html#typo3-fluid-uri-action)
    — generate action URIs for use inside attributes.
-   [f:format.date](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Format/Date.html#typo3-fluid-format-date)
    — format date objects and timestamps.
-   [f:flashMessages](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/FlashMessages.html#typo3-fluid-flashmessages)
    — render flash messages added by the controller.
-   [f:form](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Form/Index.html#typo3-fluid-form)
    — build forms with automatic `__trustedProperties` token generation.
-   [f:translate](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Translate.html#typo3-fluid-translate)
    — render localised labels from `locallang.xlf`.
-   [f:debug](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Global/Debug.html#typo3-fluid-debug)
    — dump a variable's type and value during development; remove before
    deploying to production. By default output is prepended to the page top;
    use `inline="1"` to render it in place. For deeper introspection of
    Extbase objects and lazy-loaded relations, the community extension
    [includekrexx](https://extensions.typo3.org/extension/includekrexx)
    provides a richer debug output than `f:debug`.

For the complete reference of all built-in ViewHelpers, see the
[ViewHelper reference](https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/Index.html#start).

To write your own ViewHelper, see
[Creating custom ViewHelpers](https://docs.typo3.org/permalink/t3coreapi:fluid-custom-viewhelper@main).

For reusable template fragments with a typed argument contract, Fluid v4
introduced [Fluid Components](https://docs.typo3.org/permalink/t3coreapi:using-fluid-components@main)
as an alternative to both partials and custom ViewHelpers. Components are
pure Fluid templates — no PHP class required — with typed, named arguments
declared via `<f:argument>`. They are the right choice for
UI building blocks such as buttons, cards, or form
field wrappers that need to be reused across templates.

Custom ViewHelpers are the right choice when logic cannot be expressed in
Fluid alone, or when access to the rendering context is needed — for example
ViewHelpers like `f:form.textfield` that must locate a parent
`f:form` tag. Components do not have access to the parent rendering
context and must not be used in those situations.

## `JsonView`: rendering JSON responses from Extbase

To return JSON from an action, set
`$this->defaultViewObjectName` to
`\TYPO3\CMS\Extbase\Mvc\View\JsonView::class` and then return
`$this->jsonResponse()`. Extbase will then use
`JsonView` instead of Fluid to render
the response body.

`JsonView` does not expose all assigned
variables by default. Declare which variables to include with
`setVariablesToRender()`, and set which properties of each object are
exposed with `setConfiguration()`:

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

```php
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\View\JsonView;

class ConferenceController extends ActionController
{
    protected ?string $defaultViewObjectName = JsonView::class;

    public function listAction(): ResponseInterface
    {
        $this->view->assign('conferences', $this->conferenceRepository->findAll());
        $this->view->setVariablesToRender(['conferences']);
        $this->view->setConfiguration([
            'conferences' => [
                '_descendAll' => [
                    '_only' => ['title', 'conferenceDate', 'uid'],
                ],
            ],
        ]);
        return $this->jsonResponse();
    }
}
```

The configuration keys are:

-   `_only` — allowlist of property names to include.
-   `_exclude` — denylist of property names to omit.
-   `_descend` — descend into a named sub-object and apply nested
    configuration to it.
-   `_descendAll` — apply the same nested configuration to every element
    of an array or `\TYPO3\CMS\Extbase\Persistence\ObjectStorage`.

For simple cases where the JSON structure can be built explicitly, skip
`JsonView` by passing a string
to `jsonResponse()` directly:

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

```php
public function countAction(): ResponseInterface
{
    return $this->jsonResponse(json_encode([
        'count' => $this->conferenceRepository->countAll(),
    ]));
}
```

> [!NOTE]
> **See also**
>
> [Registration: frontend plugin](https://docs.typo3.org/permalink/t3coreapi:extbase-registration-frontend-plugin@main)
> for how to register a controller action as a cacheable or non-cacheable plugin.
