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. TYPO3 tries each entry in order — first match wins, both for incoming requests and for URL generation.

Anatomy of a route entry 

EXT:my_extension/Configuration/Sets/MyExtension/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 

Routes are evaluated top to bottom. The first entry whose routePath pattern and _controller both match wins. This means a general placeholder route can accidentally swallow requests meant for a more specific route if it appears first.

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/MyExtension/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} were listed first, the request for /conferences/page/2 would match it — conference_slug would receive the value page and the pagination route would never be reached. The slug typo3camp-2025 and the static segment page are both just strings to the router; order is what distinguishes them.

Making path segments optional with defaults 

The defaults key at enhancer level makes a placeholder optional. When the generated URL would contain the default value, that segment is omitted entirely.

In the full example below, page: '1' means a link to page 1 of the list produces /conferences/ rather than /conferences/page/1. A link to page 2 still produces /conferences/page/2.

EXT:my_extension/Configuration/Sets/MyExtension/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
    defaults:
      page: '1'
    requirements:
      page: '\d+'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
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+' ensures that /conferences/page/2 matches the pagination route while /conferences/typo3camp-2025 does not — because typo3camp-2025 is not all digits, so the pagination route is skipped and the detail route matches instead.

Strict requirements also affect URL generation: combined with a StaticRangeMapper aspect, they allow TYPO3 to treat the parameter as static and omit cHash from the generated URL.

Note that requirements are ignored for any placeholder that has a corresponding aspects entry — the aspect takes precedence.

EXT:my_extension/Configuration/Sets/MyExtension/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
    defaults:
      page: '1'
    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: mapping route placeholders to URLs.