Defining routes for Extbase plugins 

Each entry in the routes list of the Extbase plugin enhancer maps one controller/action combination to a URL pattern. For an incoming request, TYPO3 tests the entries in order, and the first one that fits wins. To generate a URL, TYPO3 sorts the entries first, see How route variants are matched.

Anatomy of a route entry 

EXT:my_extension/Configuration/Sets/Main/route-enhancers.yaml
routes:
  - routePath: '/{conference_slug}'
    _controller: 'Conference::show'
    _arguments:
      conference_slug: conference
Copied!
routePath
The URL segment appended to the page slug. Static text and {placeholder} variables can be combined freely. A bare / matches the page URL with nothing appended — useful for the default action.
_controller
The controller/action pair in ControllerName::actionName format — no Action suffix, no namespace. This tells TYPO3 which route to pick when generating a URL for that action, and which action to dispatch to when resolving an incoming request.
_arguments

Maps placeholder names in routePath to Extbase argument names. In the example above, the URL segment captured by {conference_slug} is passed to the action as the argument named conference . The action receives the raw URL value — typically a slug string like typo3camp-2025. An aspect on conference_slug then translates that slug into the UID that Extbase uses to load the object. Without an aspect the raw string reaches the action unchanged.

Omit _arguments entirely when the placeholder name already matches the Extbase argument name.

Route order and specificity 

For an incoming URL, routes are evaluated top to bottom, and the first route that fits wins. A route does not fit if the URL does not match its routePath and requirements , or if one of its aspects cannot resolve the value taken from the URL. TYPO3 then tries the next route.

A placeholder without an aspect and without a requirement accepts every value of its URL segment. A route made of such a placeholder swallows the requests meant for the routes after it.

Consider a plugin with a list action, a paginated list, and a detail action. Given the page slug /conferences, the following URLs need to resolve correctly:

  • /conferences/ → Conference::list (no arguments)
  • /conferences/page-2 → Conference::list with currentPage = 2
  • /conferences/typo3camp-2025 → Conference::show with the resolved UID

With this route order, resolution works correctly:

EXT:my_extension/Configuration/Sets/Main/route-enhancers.yaml
routes:
  - routePath: '/'
    _controller: 'Conference::list'
  - routePath: '/page-{page}'
    _controller: 'Conference::list'
    _arguments:
      page: currentPage
  - routePath: '/{conference_slug}'
    _controller: 'Conference::show'
    _arguments:
      conference_slug: conference
Copied!

If /{conference_slug} had no aspect and were listed before /page-{page} , the request for /conferences/page-2 would match it. conference_slug would receive the value page-2, and the pagination route would never be reached.

With a PersistedAliasMapper on conference_slug , as in the complete examples, the order is no longer decisive for this URL: the mapper finds no conference with the slug page-2, so TYPO3 skips the route and the pagination route matches. Put routes with a static segment first all the same:

  • TYPO3 compares the static segment as a string, which rejects a URL without any further work. A PersistedAliasMapper queries the database for every URL it is asked to resolve.
  • If a value is valid for both routes, for example a conference with the slug page-2, the route listed first wins.

Separate routes instead of defaults 

The defaults key at enhancer level makes a placeholder optional: an incoming URL may leave it out, and TYPO3 fills in the default value. When generating a URL, TYPO3 leaves out a placeholder whose value is the default, but only the placeholder. The static text of the route path stays in the URL.

With defaults set to page: '1' , the route /page-{page} therefore generates /conferences/page for the first page. TYPO3 tries a route with placeholders before the route / , see How route variants are matched. So even a link to the list without any page argument gets /conferences/page instead of /conferences/.

A default value also reaches the action. With month: '' as default, a request for /conferences/2025 passes an empty month to the action.

Configure one route for each combination of arguments instead, and leave out defaults :

EXT:my_extension/Configuration/Sets/Main/route-enhancers.yaml
routeEnhancers:
  ConferencesPlugin:
    type: Extbase
    limitToPages:
      - 'page["module"] == "conferences"'
    extension: MyExtension
    plugin: Conferences
    defaultController: 'Conference::list'
    routes:
      - routePath: '/'
        _controller: 'Conference::list'
      - routePath: '/page-{page}'
        _controller: 'Conference::list'
        _arguments:
          page: currentPage
      - routePath: '/{conference_slug}'
        _controller: 'Conference::show'
        _arguments:
          conference_slug: conference
    requirements:
      page: '\d+'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
Copied!
  • A link to the list without currentPage only fits the route / , because /page-{page} needs a value for its placeholder. It generates /conferences/.
  • A link with currentPage set to 2 generates /conferences/page-2.

A link with currentPage set to 1 generates /conferences/page-1, a second URL for the first page. The pagination template could leave out the argument for the first page. The action then uses the default value of its parameter:

EXT:my_extension/Resources/Private/Templates/Conference/List.fluid.html (excerpt)
<nav aria-label="Pagination">
  <f:for each="{pagination.allPageNumbers}" as="page">
    <f:if condition="{page} == 1">
      <f:then>
        <f:link.action action="list">{page}</f:link.action>
      </f:then>
      <f:else>
        <f:link.action action="list" arguments="{currentPage: page}">
          {page}
        </f:link.action>
      </f:else>
    </f:if>
  </f:for>
</nav>
Copied!

Constraining placeholders with requirements 

requirements defines a regular expression per placeholder. A route only matches an incoming URL segment if it satisfies the requirement; without a requirement, any string matches.

In the full example below, page: '\d+' limits the pagination route to digits: /conferences/page-2 fits it, /conferences/page-two does not.

A requirement alone does not remove cHash from the generated URL, however strict it is. In the example, the StaticRangeMapper makes page static. A placeholder without an aspect needs a static declaration in addition to its requirement, see The cHash parameter.

A requirement also applies to a placeholder that has an aspect. It is checked against the value in the URL, such as a slug, not against the value the action receives, such as a UID. See Aspects and requirements together.

EXT:my_extension/Configuration/Sets/Main/route-enhancers.yaml
routeEnhancers:
  ConferencesPlugin:
    type: Extbase
    limitToPages:
      - 'page["module"] == "conferences"'
    extension: MyExtension
    plugin: Conferences
    defaultController: 'Conference::list'
    routes:
      - routePath: '/'
        _controller: 'Conference::list'
      - routePath: '/page-{page}'
        _controller: 'Conference::list'
        _arguments:
          page: currentPage
      - routePath: '/{conference_slug}'
        _controller: 'Conference::show'
        _arguments:
          conference_slug: conference
    requirements:
      page: '\d+'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
Copied!

The next step is configuring the aspects entries that translate placeholder values into human-readable URL segments — see Routing aspects.