---
title: "Developing a custom ViewHelper"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:fluid-custom-viewhelper@main"
source: "ApiOverview/Fluid/DevelopCustomViewhelper.rst"
rendered: "2026-09-19T06:56:03+00:00"
---

# Developing a custom ViewHelper {#fluid-custom-viewhelper}

This chapter demonstrates how to write a custom Fluid ViewHelper in TYPO3.

A "Gravatar" ViewHelper is created, which uses an email address as parameter
and shows the picture from gravatar.com if it exists.

The official documentation of Fluid for writing custom ViewHelpers can be found
within the Fluid documentation: [Creating ViewHelpers](https://docs.typo3.org/other/typo3fluid/fluid/main/en-us/Extending/ViewHelpers.html#creating-viewhelpers).

**Contents of this page**

-   [Fluid](https://docs.typo3.org/permalink/t3coreapi:fluid@main)
-   [AbstractViewHelper implementation](https://docs.typo3.org/permalink/t3coreapi:abstractviewhelper-implementation@main)
-   [Creating HTML/XML tags with the AbstractTagBasedViewHelper](https://docs.typo3.org/permalink/t3coreapi:creating-html-xml-tags-with-the-abstracttagbasedviewhelper@main)
-   [Insert optional arguments with default values](https://docs.typo3.org/permalink/t3coreapi:insert-optional-arguments-with-default-values@main)
-   [Prepare ViewHelper for inline syntax](https://docs.typo3.org/permalink/t3coreapi:prepare-viewhelper-for-inline-syntax-1@main)
-   [Handle additional arguments](https://docs.typo3.org/permalink/t3coreapi:handle-additional-arguments-1@main)
-   [The different render methods](https://docs.typo3.org/permalink/t3coreapi:the-different-render-methods-1@main)
-   [How to access objects in the ViewHelper implementation](https://docs.typo3.org/permalink/t3coreapi:how-to-access-objects-in-the-viewhelper-implementation@main)

## Fluid {#fluid-custom-viewhelper-fluid}

The custom ViewHelper is not part of the default distribution. Therefore a
namespace import is necessary to use this ViewHelper. In the following example,
the namespace `\MyVendor\MyExtension\ViewHelpers` is imported with the
prefix `m`. Now, all tags starting with `m:` are interpreted as
ViewHelper from within this namespace.
For further information about namespace import, see
[ViewHelper namespaces](https://docs.typo3.org/permalink/t3coreapi:fluid-syntax-viewhelpers-import-namespaces@main).

The ViewHelper should be given the name "gravatar" and take an email
address and an optional alt-text  as a parameters.
The ViewHelper is called in the template as follows:

**EXT:my_extension/Resources/Private/Templates/SomeTemplate.fluid.html**

```html
<html
  xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  xmlns:m="http://typo3.org/ns/MyVendor/MyExtension/ViewHelpers"
  data-namespace-typo3-fluid="true"
>
  <m:gravatar emailAddress="username@example.org" alt="Gravatar icon of user" />
</html>

```

## `AbstractViewHelper` implementation {#fluid-custom-viewhelper-implementation}

Every ViewHelper is a PHP class. For the Gravatar ViewHelper, the fully
qualified name of the class is
`\MyVendor\MyExtension\ViewHelpers\GravatarViewHelper`.

**Example 1: EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

final class GravatarViewHelper extends AbstractViewHelper
{
  protected $escapeOutput = false;

  public function initializeArguments(): void
  {
    // registerArgument($name, $type, $description, $required, $defaultValue, $escape)
    $this->registerArgument(
      'emailAddress',
      'string',
      'The email address to resolve the gravatar for',
      true,
    );
    $this->registerArgument(
      'alt',
      'string',
      'The optional alt text for the image',
    );
  }

  public function render(): string
  {
    $emailAddress = $this->arguments['emailAddress'];
    $altText = $this->arguments['alt'] ?? '';

    // this is improved with the TagBasedViewHelper (see below)
    return sprintf(
      '<img src="https://www.gravatar.com/avatar/%s" alt="%s">',
      md5($emailAddress),
      htmlspecialchars($altText),
    );
  }
}

```

### `AbstractViewHelper` {#fluid-custom-viewhelper-abstractviewhelper}

*line 9* `extends AbstractViewHelper`

Every ViewHelper must inherit from the class
`\TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper`.

A ViewHelper can also inherit from subclasses of
`AbstractViewHelper`, for example
from `\TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper`.
Several subclasses are offering additional functionality. The
`AbstractTagBasedViewHelper`
will be explained [later on in this chapter](https://docs.typo3.org/permalink/t3coreapi:creating-xml-tags-using-tagbasedviewhelper@main) in detail.

### Disable escaping the output {#fluid-viewhelper-custom-escaping-of-output}

*line 11* `protected $escapeOutput = false;`

By default, all output is escaped by `htmlspecialchars()` to prevent cross
site scripting.

Setting the property `$escapeOutput` to false is necessary to prevent
escaping of ViewHelper output.

By setting the property `$escapeChildren` to false, escaping of the tag
content (its child nodes) can be disabled. If this is not set explicitly,
the value will be determined automatically: If `$escapeOutput`: is true,
`$escapeChildren` will be disabled to prevent double escaping. If
`$escapeOutput`: is false, `$escapeChildren` will be enabled unless
disabled explicitly.

Passing in children is explained in [Prepare ViewHelper for inline syntax](https://docs.typo3.org/permalink/t3coreapi:prepare-viewhelper-for-inline-syntax@main).

### `initializeArguments()` {#fluid-viewhelper-custom-initializearguments}

*line 13* `public function initializeArguments(): void`

The `Gravatar` ViewHelper must hand over the email address which
identifies the Gravatar. An alt text for the image is passed as optional parameter.

ViewHelpers have to register (line 16, `$this->registerArgument()`) parameters.
The registration happens inside method `initializeArguments()`.

In the example above, the ViewHelper receives the argument `emailAddress` (line 17) of
type `string` (line 18) which is mandatory (line 19). The optional argument `alt`
is defined in lines 22-26.

These arguments can be accessed
through the array `$this->arguments`, in method `render()`.

> [!TIP]
> If arguments can take various types, use a union type, for example
> `array|string`. If any type should be allowed, use `mixed`
> instead.

<!-- TODO: no Markdown rendering for "versionadded" -->

Union types for ViewHelper arguments are available since TYPO3 14.0.

### `render()` {#fluid-custom-viewhelper-implementation-render}

*line 29* `public function render(): string`

The method `render()` is called once the ViewHelper is rendered. Its return
value can be directly output in Fluid or passed to another ViewHelper.

In *line 30 an 31* we retrieve the arguments from the `$arguments` class
property. `alt` is an optional argument and therefore nullable. Fluid ensures,
the declared type is passed for non-null values. These arguments can contain
user input.

> [!WARNING]
> **Attention**
>
> Prevent [XSS (Cross-Site Scripting) attacks](https://docs.typo3.org/permalink/t3coreapi:prevent-cross-site-scripting@main).
>
> The returned string is displayed raw, without being passed through
> `htmlspecialchars()` as we have
> [disabled escaping](https://docs.typo3.org/permalink/t3coreapi:fluid-viewhelper-custom-escaping-of-output@main).

If escaping is disabled, the `render()` method is responsible for preventing
[XSS attacks](https://docs.typo3.org/permalink/t3coreapi:prevent-cross-site-scripting@main).

Therefore all arguments **must** be sanitized before they are returned.

Passing the email address through
[md5()](https://www.php.net/manual/en/function.md5.php) ensures that
we only have a hexadecimal number, it can contain no harmful chars.

The alt text is passed through `htmlspecialchars()`, therefore potentially
harmful chars are escaped.

> [!TIP]
> **Hint**
>
> The `render()` method usually returns a string that should be displayed
> directly in the Fluid template. But it is also possible to return another
> type and use it as argument for another ViewHelper which expects that type.

## Creating HTML/XML tags with the `AbstractTagBasedViewHelper` {#creating-html-tags-using-tagbasedviewhelper}

For ViewHelpers which create HTML/XML tags, Fluid provides an enhanced base
class: `\TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper`.  This
base class provides an instance of
`\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder` that can be used to create
HTML-tags. It takes care of the syntactically correct creation and, for example,
escapes single and double quotes in attribute values.

*line 11* `protected $tagName = 'img'` configures the name of the HTML/XML tag
to be output.

All ViewHelpers extending `AbstractTagBasedViewHelper`
can receive arbitrary tag attributes which will be appended to the resulting
HTML tag and escaped automatically. For example we do not have to declare or
escape the `alt` argument as we did in
[Example 1](https://docs.typo3.org/permalink/t3coreapi:fluid-custom-viewhelper-implementation@main).

Because the Gravatar ViewHelper creates an `<img>` tag the use of the
`TagBuilder`, stored in class property
`$this->tag` is advised:

**EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php (Example 2, tag-based)**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
  protected $tagName = 'img';

  public function initializeArguments(): void
  {
    parent::initializeArguments();
    $this->registerArgument(
      'emailAddress',
      'string',
      'The email address to resolve the gravatar for',
      true,
    );
    // The alt argument will be automatically registered
  }

  public function render(): string
  {
    $emailAddress = $this->arguments['emailAddress'];
    $this->tag->addAttribute(
      'src',
      'https://www.gravatar.com/avatar/' . md5($emailAddress),
    );
    return $this->tag->render();
  }
}

```

*line 32* `$this->tag->render()` creates the `<img>` tag with all
explicitly added arguments  (*line 28-31* `$this->tag->addAttribute()`) and all
arbitrary tag attributes passed to the ViewHelper when it is used.

### `AbstractTagBasedViewHelper` {#abstracttagbasedviewhelper}

*line 6* `class GravatarViewHelper extends AbstractTagBasedViewHelper`

The ViewHelper does not inherit directly from
`AbstractViewHelper` but
from `AbstractTagBasedViewHelper`,
which provides and initializes the
`TagBuilder` and passes on and
escapes arbitrary tag attributes.

### `$tagName` {#abstracttagbasedviewhelper-tagname}

*line 9* `protected $tagName = 'img';`

There is a class property `$tagName` which stores the name of the tag to be
created (`<img>`).

### `$this->tag->addAttribute()` {#abstracttagbasedviewhelper-addattribute}

*line 28 - 31* `$this->tag->addAttribute(...)`

The tag builder is available as class property `$this->tag`. It offers
the method `TagBuilder::addAttribute()` to add new tag attributes. In our
example the attribute `src` is added to the tag.

### `$this->tag->render()` {#abstracttagbasedviewhelper-render}

*line 32* `return $this->tag->render();`

The GravatarViewHelper creates an img tag builder, which has a method named
`render()`. After configuring the tag builder instance, the rendered tag
markup is returned.

## Insert optional arguments with default values {#insert-optional-arguments}

An optional size for the image can be provided to the Gravatar ViewHelper. This
size parameter will determine the height and width in pixels of the
image and can range from 1 to 512. When no size is given, an image of 80px is
generated.

The `render()` method can be improved like this:

**EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
  public function initializeArguments(): void
  {
    $this->registerArgument(
      'emailAddress',
      'string',
      'The email address to resolve the gravatar for',
      true,
    );
    $this->registerArgument(
      'size',
      'integer',
      'The size of the gravatar, ranging from 1 to 512',
      false,
      80,
    );
  }

  public function render(): string
  {
    $emailAddress = $this->arguments['emailAddress'];
    $size = $this->arguments['size'];
    $this->tag->addAttribute(
      'src',
      sprintf(
        'http://www.gravatar.com/avatar/%s?s=%s',
        md5($emailAddress),
        urlencode($size),
      ),
    );
    return $this->tag->render();
  }
}

```

With this setting of a default value and setting the fourth argument to `false`,
the `size` attribute becomes optional.

## Prepare ViewHelper for inline syntax {#prepare-viewhelper-for-inline-syntax}

So far, the Gravatar ViewHelper has focused on the tag structure of the
ViewHelper. The call to render the ViewHelper was written with tag syntax, which
seemed obvious because it itself returns a tag:

**EXT:my_extension/Resources/Private/Templates/SomeTemplate.fluid.html**

```html
<m:gravatar emailAddress="{post.author.emailAddress}" />
```

Alternatively, this expression can be written using the inline notation:

**EXT:my_extension/Resources/Private/Templates/SomeTemplate.fluid.html**

```html
{m:gravatar(emailAddress: post.author.emailAddress)}
```

One should see the Gravatar ViewHelper as a kind of post-processor for an email
address and would allow the following syntax:

**EXT:my_extension/Resources/Private/Templates/SomeTemplate.fluid.html**

```html
{post.author.emailAddress -> m:gravatar()}
```

This syntax places focus on the variable that is passed to the ViewHelper as it
comes first.

The syntax `{post.author.emailAddress -> m:gravatar()}` is an alternative
syntax for `<m:gravatar>{post.author.emailAddress}</m:gravatar>`. To
support this, the email address comes either from the argument `emailAddress`
or, if it is empty, the content of the tag should be interpreted as email
address.

This is typically used with formatting ViewHelpers. These ViewHelpers all
support both tag mode and inline syntax.

Depending on the implemented method for rendering, the implementation is
different:

To fetch the content of the ViewHelper the method `renderChildren()` is
available in the `AbstractViewHelper`.
This returns the evaluated object between the opening and closing tag.

**EXT:my_extension/Classes/ViewHelpers/GravatarViewHelper.php  (Example 3, with content arguments)**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\ViewHelpers;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;

final class GravatarViewHelper extends AbstractTagBasedViewHelper
{
  protected $tagName = 'img';

  public function initializeArguments(): void
  {
    $this->registerArgument(
      'emailAddress',
      'string',
      'The email address to resolve the gravatar for',
      // The argument is optional now
    );
  }

  public function render(): string
  {
    $emailAddress = $this->renderChildren();

    // The children of the ViewHelper might be empty now
    if ($emailAddress === null) {
      throw new \Exception(
        'The Gravatar ViewHelper expects either the '
          . 'argument "emailAddress" or the content to be set. ',
        1726035545,
      );
    }
    // Or someone could pass a non-string value
    if (!is_string($emailAddress) || !filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
      throw new \Exception(
        'The Gravatar ViewHelper expects a valid ' .
          'e-mail address as input. ',
        1726035546,
      );
    }

    $this->tag->addAttribute(
      'src',
      'https://www.gravatar.com/avatar/' . md5($emailAddress),
    );

    return $this->tag->render();
  }

  public function getContentArgumentName(): string
  {
    return 'emailAddress';
  }
}

```

## Handle additional arguments {#handle-additional-arguments}

All ViewHelpers implementing
`AbstractTagBasedViewHelper`
can receive arbitrary tag attributes which will be appended to the
resulting HTML tag. In the past, this was only possible for
explicitly registered arguments.

## The different render methods {#the-different-render-methods}

ViewHelpers can have one or more of the following methods for
implementing the rendering. The following section will describe the differences
between the implementations.

### `render()` method {#render-method}

This method is usually implemented.

### `compile()`-Method {#compile-method}

This method can be overwritten to define how the ViewHelper should be compiled.
That can make sense if the ViewHelper itself is a wrapper for another native PHP
function or TYPO3 function. In that case, the method can return the call to this
function and remove the need to call the ViewHelper as a wrapper at all.

The `compile()` has to return the compiled PHP code for the ViewHelper.
Also the argument `$initializationPhpCode` can be used to add further PHP
code before the execution.

Example implementation:

**EXT:my_extension/Classes/ViewHelpers/StrtolowerViewHelper.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\BlogExample\ViewHelpers;

use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

final class StrtolowerViewHelper extends AbstractViewHelper
{
  public function initializeArguments(): void
  {
    $this->registerArgument('string', 'string', 'The string to lowercase.', true);
  }

  public function compile(
    $argumentsName,
    $closureName,
    &$initializationPhpCode,
    ViewHelperNode $node,
    TemplateCompiler $compiler,
  ): string {
    return sprintf("strtolower(%s['string'])", $argumentsName);
  }
}

```

## How to access objects in the ViewHelper implementation {#fluid-custom-viewhelper-access}

Custom ViewHelper implementations support
[Dependency injection](https://docs.typo3.org/permalink/t3coreapi:dependency-injection@main).

You can, for example, inject the `ExtensionConfiguration`
service to get the configuration of an installed extension. Note that ViewHelpers
should avoid getting additional data from the database because this
violates the separation of data (model) and presentation (view).

Some objects depend on the current context and can be fetched from
the rendering context:

### Accessing the current request in a ViewHelper implementation {#fluid-custom-viewhelper-access-request}

The current
`ServerRequestInterface` object can be fetched
from the `RenderingContext` inside
the `render()` method in a ViewHelper implementation,:

**EXT:my_extension/Classes/ViewHelpers/SomeViewHelper.php**

```php
use Psr\Http\Message\ServerRequestInterface;

public function render(): string
{
    if ($this->renderingContext->hasAttribute(ServerRequestInterface::class)) {
        $request = $this->renderingContext->getAttribute(ServerRequestInterface::class);
    }
    return 'Hello World!';
}
```

Note that the request may not be available in all Fluid template contexts, so
use `hasAttribute()` first to check if it exists.

The request may contain other attributes that are relevant for your
ViewHelper's use case. See
[TYPO3 request attributes](https://docs.typo3.org/permalink/t3coreapi:request-attributes@main)
for a list of available attributes.

### Using `stdWrap` / fetching the current `ContentObject` in a ViewHelper implementation {#fluid-custom-viewhelper-access-contentobject}

You can access the `ContentObjectRenderer`
from the `ServerRequestInterface`:

**EXT:my_extension/Classes/ViewHelpers/SomeViewHelper.php**

```php
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

public function render(): string
{
    if ($this->renderingContext->hasAttribute(ServerRequestInterface::class)) {
        $request = $this->renderingContext->getAttribute(ServerRequestInterface::class);
        $cObj = $request->getAttribute('currentContentObject');
        return $cObj->stdWrap('Hello World', ['wrap' => '|!']);
    }
    return '';
}
```
