Generating URLs with the UriBuilder 

The UriBuilder generates URLs for Extbase actions. When routing configuration is in place, it produces the clean URL defined by the matching route variant. Without routing configuration it produces the raw namespaced query string.

The UriBuilder is available in two contexts: inside a controller action via $this->uriBuilder, and inside Fluid templates via <f:link.action> and <f:uri.action>.

Generating URLs in a controller action 

Use uriFor() to generate a URL for any action. It is available as $this->uriBuilder->uriFor() in any controller action:

EXT:my_extension/Classes/Controller/ConferenceController.php
<?php

namespace MyVendor\MyExtension\Controller;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ConferenceController extends ActionController
{
    public function listAction(): ResponseInterface
    {
        $conferences = $this->conferenceRepository->findAll();

        // Link to the detail action on the same plugin/page,
        // using the first conference as an example
        $uri = $this->uriBuilder->uriFor(
            'show',                                  // action name — no 'Action' suffix
            ['conference' => $conferences->getFirst()],
            'Conference',                            // controller name
        );

        $this->view->assign('conferences', $conferences);
        $this->view->assign('detailUri', $uri);
        return $this->htmlResponse();
    }
}
Copied!

uriFor() signature:

uriFor(
    ?string $actionName = null,
    ?array $controllerArguments = null,
    ?string $controllerName = null,
    ?string $extensionName = null,
    ?string $pluginName = null,
): string
Copied!

All parameters are optional. When omitted, the current controller, extension, and plugin are used. Only pass $extensionName and $pluginName when linking to a different plugin than the one handling the current request.

Linking to a plugin on a different page 

The most common routing mistake: list and detail are on separate pages, but the list template generates detail links without telling the UriBuilder which page to target. The result is a link back to the list page instead of the detail page.

Use setTargetPageUid() before calling uriFor(). reset() clears all previously set options — call it before each URL generation inside a loop:

EXT:my_extension/Classes/Controller/ConferenceController.php
use MyVendor\MyExtension\Domain\Model\Conference;
use Psr\Http\Message\ResponseInterface;

class ConferenceController extends ActionController
{
    public function listAction(): ResponseInterface
    {
        $conferences = $this->conferenceRepository->findAll();

        foreach ($conferences as $conference) {
            $uri = $this->uriBuilder
                ->reset()
                ->setTargetPageUid(42)   // UID of the detail page
                ->uriFor('show', ['conference' => $conference], 'Conference');

            // …
        }

        return $this->htmlResponse();
    }
}
Copied!

The detail page UID is typically stored in TypoScript settings (or site set settings) so it does not need to be hardcoded:

EXT:my_extension/Classes/Controller/ConferenceController.php
$detailPageUid = (int)($this->settings['detailPageUid'] ?? 0);

$uri = $this->uriBuilder
    ->reset()
    ->setTargetPageUid($detailPageUid)
    ->uriFor('show', ['conference' => $conference], 'Conference');
Copied!

Absolute URLs 

For use in emails, JSON responses, or redirects, generate an absolute URL:

EXT:my_extension/Classes/Controller/ConferenceController.php
$uri = $this->uriBuilder
    ->reset()
    ->setCreateAbsoluteUri(true)
    ->uriFor('show', ['conference' => $conference], 'Conference');
Copied!

Generating URLs in Fluid templates 

<f:link.action> and <f:uri.action> are the Fluid equivalents of uriFor(). When routing configuration is in place, they produce the same clean URLs automatically.

EXT:my_extension/Resources/Private/Templates/Conference/List.fluid.html
<f:link.action action="show" controller="Conference" arguments="{conference: conference}">
    {conference.title}
</f:link.action>
Copied!

To link to a plugin on a different page, use pageUid:

EXT:my_extension/Resources/Private/Templates/Conference/List.fluid.html
<f:link.action
    action="show"
    controller="Conference"
    arguments="{conference: conference}"
    pageUid="{settings.detailPageUid}"
>
    {conference.title}
</f:link.action>
Copied!

For a plain URL string without an <a> tag, use <f:uri.action> with the same attributes — useful when you need to construct the link yourself:

EXT:my_extension/Resources/Private/Templates/Conference/List.fluid.html
<a href="{f:uri.action(action: 'show', controller: 'Conference', arguments: '{conference: conference}', pageUid: settings.detailPageUid)}"
   title="{conference.title}"
   class="conference-link">{conference.title}</a>
Copied!

Why is my URL not clean? 

If a URL comes out as a raw query string with cHash instead of the expected clean URL, work through this list:

  • No enhancer configured — the plugin has no entry under routeEnhancers in EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml or config/sites/<site-identifier>/config.yaml.
  • Wrong page setTargetPageUid() (or pageUid in Fluid) is missing or points to the wrong page. The enhancer's limitToPages must include the target page UID or match the page via an expression.
  • No matching route variant — the controller/action combination passed to uriFor() does not match any _controller entry in the routes list in EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml.
  • Missing aspect — a placeholder has a \d+ requirement but no StaticRangeMapper or StaticValueMapper aspect, so TYPO3 cannot treat the parameter as static and appends cHash.
  • Stale cache — after changing the site configuration, clear all caches via Admin Tools > Maintenance.

With URLs generating correctly, see Routing examples and common mistakes for complete worked examples covering the most common plugin configurations.