---
title: "Routing examples and common mistakes"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-routing-examples@main"
source: "ExtensionArchitecture/Extbase/Routing/Examples.rst"
rendered: "2026-09-22T15:26:06+00:00"
---

# Routing examples and common mistakes {#extbase-routing-examples}

The following examples build on the concepts explained in the preceding pages.
Each is a complete, working configuration you can adapt directly.

**On this page**

-   [List, pagination, and detail](https://docs.typo3.org/permalink/t3coreapi:list-pagination-and-detail@main)
-   [List and detail on separate pages](https://docs.typo3.org/permalink/t3coreapi:list-and-detail-on-separate-pages@main)
-   [Multiple controllers in one plugin](https://docs.typo3.org/permalink/t3coreapi:multiple-controllers-in-one-plugin@main)
-   [Date-based conference archive](https://docs.typo3.org/permalink/t3coreapi:date-based-conference-archive@main)
-   [Beyond built-in aspects](https://docs.typo3.org/permalink/t3coreapi:beyond-built-in-aspects@main)
-   [Common mistakes](https://docs.typo3.org/permalink/t3coreapi:common-mistakes@main)

## List, pagination, and detail {#extbase-routing-examples-list-detail}

The most common Extbase plugin pattern: a list page with pagination and a
detail view. The list and detail actions are on the same page here; for
separate pages see [List and detail on separate pages](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-examples-separate-pages@main).

**URLs produced:**

-   `/conferences/` — list, first page
-   `/conferences/page-2` — list, page 2
-   `/conferences/typo3camp-2025` — detail view

**EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml**

```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'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
        routeValuePrefix: '/'
        fallbackValue: null

```

Key points:

-   The pagination route `/page-{page}` uses a static prefix `page-`
    to distinguish it from the detail route `/{conference_slug}`. Without
    the prefix, `/conferences/page-2` would match the detail route first
    and try to resolve `page-2` as a conference slug.
-   `fallbackValue: null` on the `PersistedAliasMapper` means a
    deleted or hidden conference returns `null` to the action rather
    than a 404. The action must declare the argument nullable and handle it
    explicitly — see [Handling deleted or hidden records](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-aspects-fallback@main).
-   Route order matters: pagination before detail, most specific before most
    general — see [Route order and specificity](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-routes-order@main).

## List and detail on separate pages {#extbase-routing-examples-separate-pages}

When the list plugin and the detail plugin live on different pages, each page
needs its own enhancer entry — `limitToPages` must point to the correct
page for each, and links between them need `setTargetPageUid()` or
`pageUid` in Fluid.

**EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml**

```yaml
routeEnhancers:
  ConferencesList:
    type: Extbase
    limitToPages:
      - 'page["module"] == "conferences_list"'
    extension: MyExtension
    plugin: Conferences
    defaultController: 'Conference::list'
    routes:
      - routePath: '/'
        _controller: 'Conference::list'
      - routePath: '/page-{page}'
        _controller: 'Conference::list'
        _arguments:
          page: currentPage
    defaults:
      page: '1'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
  ConferencesDetail:
    type: Extbase
    limitToPages:
      - 42
    extension: MyExtension
    plugin: Conferences
    defaultController: 'Conference::show'
    routes:
      - routePath: '/{conference_slug}'
        _controller: 'Conference::show'
        _arguments:
          conference_slug: conference
    aspects:
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
        routeValuePrefix: '/'
        fallbackValue: null

```

In the list template, link to the detail page explicitly:

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

```html
<f:link.action
  action="show"
  controller="Conference"
  arguments="{conference: conference}"
  pageUid="{settings.detailPageUid}"
>
  {conference.title}
</f:link.action>

```

Store the detail page UID in TypoScript settings so it is configurable
without touching PHP:

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

```typoscript
plugin.tx_myextension_conferences.settings.detailPageUid = 42
```

## Multiple controllers in one plugin {#extbase-routing-examples-multi-controller}

A single plugin can expose actions from more than one controller — all under
the same namespace, all in one enhancer. The key is that every controller/action
combination that needs a clean URL gets its own route entry.

**URLs produced:**

-   `/conferences/` — conference list
-   `/conferences/typo3camp-2025` — conference detail
-   `/conferences/typo3camp-2025/talks` — talk list for that conference
-   `/conferences/typo3camp-2025/talks/extbase-routing-demystified` — talk detail

**EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml**

```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
      - routePath: '/{conference_slug}/talks'
        _controller: 'Talk::list'
        _arguments:
          conference_slug: conference
      - routePath: '/{conference_slug}/talks/{talk_slug}'
        _controller: 'Talk::show'
        _arguments:
          conference_slug: conference
          talk_slug: talk
    defaults:
      page: '1'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
        routeValuePrefix: '/'
        fallbackValue: null
      talk_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_talk
        routeFieldName: slug
        routeValuePrefix: '/'
        fallbackValue: null

```

The `{conference_slug}` placeholder appears in both the
`Conference::show` route and the `Talk` routes. Each is a separate
route entry with its own `_controller` — the enhancer matches on the full
path and controller/action combination, not on the placeholder name alone.

## Date-based conference archive {#extbase-routing-examples-date-filter}

Filter the conference list by year and month using human-readable URL segments.
Each combination of arguments needs its own route entry because TYPO3 cannot
derive missing arguments from partial matches.

**URLs produced:**

-   `/conferences/2025/march` — conferences in March 2025
-   `/conferences/2025/march/page-2` — paginated
-   `/conferences/2025` — all conferences in 2025

**EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml**

```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: '/{year}/{month}/page-{page}'
        _controller: 'Conference::list'
        _arguments:
          year: overwriteDemand/year
          month: overwriteDemand/month
          page: currentPage
      - routePath: '/{year}/{month}'
        _controller: 'Conference::list'
        _arguments:
          year: overwriteDemand/year
          month: overwriteDemand/month
      - routePath: '/{year}'
        _controller: 'Conference::list'
        _arguments:
          year: overwriteDemand/year
      - routePath: '/article/{conference_slug}'
        _controller: 'Conference::show'
        _arguments:
          conference_slug: conference
    defaults:
      page: '1'
      month: ''
      year: ''
    requirements:
      page: '\d+'
      month: '\d+'
      year: '\d{4}'
    aspects:
      page:
        type: StaticRangeMapper
        start: '1'
        end: '50'
      month:
        type: StaticValueMapper
        map:
          january: '01'
          february: '02'
          march: '03'
          april: '04'
          may: '05'
          june: '06'
          july: '07'
          august: '08'
          september: '09'
          october: '10'
          november: '11'
          december: '12'
      year:
        type: StaticRangeMapper
        start: '2000'
        end: '2030'
      conference_slug:
        type: PersistedAliasMapper
        tableName: tx_myextension_domain_model_conference
        routeFieldName: slug
        routeValuePrefix: '/'

```

> [!WARNING]
> Each `StaticRangeMapper` is limited to 1000 values: TYPO3 throws a
> `\LengthException` when the range between `start` and `end`
> is larger than 1000 items. This guards against brute-force requests and
> cache-flooding. The ranges above stay well within that limit (31 years,
> 50 pages); for a wider span, narrow the range or write a custom mapper.

## Beyond built-in aspects {#extbase-routing-examples-custom-aspects}

The four built-in aspect types —
[PersistedAliasMapper](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-aspects-persisted-alias@main),
[PersistedPatternMapper](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-aspects-persisted-pattern@main),
[StaticValueMapper](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-aspects-static-value@main), and
[StaticRangeMapper](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-aspects-static-range@main) — cover the
majority of real-world cases. When they do not, TYPO3 allows extensions to
register fully custom aspect classes. A good example is
[`georgringer/news`](https://packagist.org/packages/georgringer/news), which ships its own `NewsTitle`,
`NewsCategory`, and `NewsTag` mappers — all thin wrappers around
`PersistedAliasMapper`
that add extension-specific defaults and fallback handling.

> [!NOTE]
> **See also**
>
> [Route Enhancements and Aspects](https://docs.typo3.org/permalink/t3coreapi:routing-advanced-routing-configuration@main) —
> the Core routing reference covers custom aspect registration.

## Common mistakes {#extbase-routing-examples-mistakes}

-   ****Plugin not placed on the target page****

    The enhancer is configured, the URL looks right, but TYPO3 returns a 404.
    Check that the plugin content element is actually present on the page
    referenced in `limitToPages`.

-   ****setTargetPageUid() missing****

    Links from a list plugin point back to the list page instead of the detail
    page. Always set `setTargetPageUid()` in PHP or `pageUid` in
    Fluid when list and detail are on separate pages.

-   ****Wrong route order****

    A catch-all placeholder route (`/{slug}`) is listed before a more
    specific route (`/page-{page}`). The catch-all matches first and the
    specific route is never reached. Most-specific routes must come first —
    see [Route order and specificity](https://docs.typo3.org/permalink/t3coreapi:extbase-routing-routes-order@main).

-   ****limitToPages missing****

    Without `limitToPages`, TYPO3 evaluates the enhancer for every page
    on the site. Performance degrades and unintended matches on unrelated pages
    become possible.

-   ****cHash still appearing****

    A placeholder has a `requirements` regex but no aspect. Only
    `StaticRangeMapper` and `StaticValueMapper` mark a parameter
    as static and suppress `cHash`. A regex requirement alone does not.

-   ****Stale cache after config changes****

    Site configuration is cached. After any change to
    [`EXT:my_extension/Configuration/Sets/MyExtension/route-enhancers.yaml`](../../FileStructure/Configuration/Sets/Index.md#file-set-route-enhancers-yaml)
    or `config/sites/<site-identifier>/config.yaml`, clear all caches
    via **Admin Tools > Maintenance**.
