Attention

TYPO3 v9 has reached its end-of-life September 30th, 2021 and is not maintained by the community anymore. Looking for a stable version? Use the version switch on the top left.

You can order Extended Long Term Support (ELTS) here: TYPO3 ELTS.

Collection of various routing examples

Note

If you have additional examples and are willing to share, please create a Pull Request on Github and add it to this page.

EXT: News

Prerequisites:

The plugins for list view and detail view are on separate pages. If you use the category menu or tag list plugins to filter news records, their titles (slugs) are used.

Result:

  • Detail view: https://example.org/news/detail/the-news-title

  • Pagination: https://example.org/news/page-2

  • Category filter: https://example.org/news/my-category

  • Tag filter: https://example.org/news/my-tag

 1routeEnhancers:
 2  News:
 3    type: Extbase
 4    extension: News
 5    plugin: Pi1
 6    routes:
 7      - routePath: '/page-{page}'
 8        _controller: 'News::list'
 9        _arguments:
10          page: '@widget_0/currentPage'
11      - routePath: '/{news-title}'
12        _controller: 'News::detail'
13        _arguments:
14          news-title: news
15      - routePath: '/{category-name}'
16        _controller: 'News::list'
17        _arguments:
18          category-name: overwriteDemand/categories
19      - routePath: '/{tag-name}'
20        _controller: 'News::list'
21        _arguments:
22          tag-name: overwriteDemand/tags
23    defaultController: 'News::list'
24    defaults:
25      page: '0'
26    aspects:
27      news-title:
28        type: PersistedAliasMapper
29        tableName: tx_news_domain_model_news
30        routeFieldName: path_segment
31      page:
32        type: StaticRangeMapper
33        start: '1'
34        end: '100'
35      category-name:
36        type: PersistedAliasMapper
37        tableName: sys_category
38        routeFieldName: slug
39      tag-name:
40        type: PersistedAliasMapper
41        tableName: tx_news_domain_model_tag
42        routeFieldName: slug

For more examples and background information see News manual.

EXT: Blog with custom Aspect

Taken from https://typo3.com routing configuration and the blog extension.

Archive

 1routeEnhancers:
 2  BlogArchive:
 3     type: Extbase
 4     extension: Blog
 5     plugin: Archive
 6     routes:
 7     -
 8         routePath: '/{year}'
 9         _controller: 'Post::listPostsByDate'
10         _arguments:
11         year: year
12     -
13         routePath: '/{year}/page-{page}'
14         _controller: 'Post::listPostsByDate'
15         _arguments:
16         year: year
17         page: '@widget_0/currentPage'
18     -
19         routePath: '/{year}/{month}'
20         _controller: 'Post::listPostsByDate'
21         _arguments:
22         year: year
23         month: month
24     -
25         routePath: '/{year}/{month}/page-{page}'
26         _controller: 'Post::listPostsByDate'
27         _arguments:
28         year: year
29         month: month
30         page: '@widget_0/currentPage'
31     defaultController: 'Post::listPostsByDate'
32     requirements:
33     year: '[0-9]{1..4}'
34     month: '[a-z]+'
35     page: '\d+'
36     aspects:
37     year:
38         type: BlogStaticDatabaseMapper
39         table: 'pages'
40         field: 'crdate_year'
41         groupBy: 'crdate_year'
42         where:
43         doktype: 137
44     month:
45         type: StaticValueMapper
46         map:
47         january: 1
48         february: 2
49         march: 3
50         april: 4
51         may: 5
52         june: 6
53         july: 7
54         august: 8
55         september: 9
56         october: 10
57         november: 11
58         december: 12
59         localeMap:
60         -
61             locale: 'de_.*'
62             map:
63             januar: 1
64             februar: 2
65             maerz: 3
66             april: 4
67             mai: 5
68             juni: 6
69             juli: 7
70             august: 8
71             september: 9
72             oktober: 10
73             november: 11
74             dezember: 12
75         -
76             locale: 'fr_.*'
77             map:
78             janvier: 1
79             fevrier: 2
80             mars: 3
81             avril: 4
82             mai: 5
83             juin: 6
84             juillet: 7
85             aout: 8
86             septembre: 9
87             octobre: 10
88             novembre: 11
89             decembre: 12
90     page:
91         type: StaticRangeMapper
92         start: '1'
93         end: '99'

Posts by Author

 1 routeEnhancers:
 2     AuthorPosts:
 3         type: Extbase
 4         extension: Blog
 5         plugin: AuthorPosts
 6         routes:
 7         -
 8             routePath: '/{author_title}'
 9             _controller: 'Post::listPostsByAuthor'
10             _arguments:
11                 author_title: author
12         -
13             routePath: '/{author_title}/page-{page}'
14             _controller: 'Post::listPostsByAuthor'
15             _arguments:
16                 author_title: author
17                 page: '@widget_0/currentPage'
18         defaultController: 'Post::listPostsByAuthor'
19         requirements:
20         author_title: '^[a-z0-9].*$'
21         page: '\d+'
22         aspects:
23         author_title:
24             type: PersistedAliasMapper
25             tableName: 'tx_blog_domain_model_author'
26             routeFieldName: 'slug'
27         page:
28             type: StaticRangeMapper
29             start: '1'
30             end: '99'

Category pages

 1 routeEnhancers:
 2     BlogCategory:
 3         type: Extbase
 4         extension: Blog
 5         plugin: Category
 6         routes:
 7         -
 8             routePath: '/{category_title}'
 9             _controller: 'Post::listPostsByCategory'
10             _arguments:
11                 category_title: category
12         -
13             routePath: '/{category_title}/page-{page}'
14             _controller: 'Post::listPostsByCategory'
15             _arguments:
16                 category_title: category
17                 page: '@widget_0/currentPage'
18         defaultController: 'Post::listPostsByCategory'
19         requirements:
20             category_title: '^[a-z0-9].*$'
21             page: '\d+'
22         aspects:
23             category_title:
24                 type: PersistedAliasMapper
25                 tableName: sys_category
26                 routeFieldName: 'slug'
27             page:
28                 type: StaticRangeMapper
29                 start: '1'
30                 end: '99'

Blog Feeds

 1routeEnhancers:
 2   PageTypeSuffix:
 3     type: PageType
 4     map:
 5         'blog.recent.xml': 200
 6         'blog.category.xml': 210
 7         'blog.tag.xml': 220
 8         'blog.archive.xml': 230
 9         'blog.comments.xml': 240
10         'blog.author.xml': 250

Blog Posts

 1routeEnhancers:
 2  BlogPosts:
 3     type: Extbase
 4     extension: Blog
 5     plugin: Posts
 6     routes:
 7     -
 8         routePath: '/page-{page}'
 9         _controller: 'Post::listRecentPosts'
10         _arguments:
11             page: '@widget_0/currentPage'
12     defaultController: 'Post::listRecentPosts'
13     requirements:
14         page: '\d+'
15     aspects:
16         page:
17             type: StaticRangeMapper
18             start: '1'
19             end: '99'

Posts by Tag

 1routeEnhancers:
 2   BlogTag:
 3     type: Extbase
 4     extension: Blog
 5     plugin: Tag
 6     routes:
 7     -
 8         routePath: '/{tag_title}'
 9         _controller: 'Post::listPostsByTag'
10         _arguments:
11         tag_title: tag
12     -
13         routePath: '/{tag_title}/page-{page}'
14         _controller: 'Post::listPostsByTag'
15         _arguments:
16         tag_title: tag
17         page: '@widget_0/currentPage'
18     defaultController: 'Post::listPostsByTag'
19     requirements:
20         tag_title: '^[a-z0-9].*$'
21         page: '\d+'
22     aspects:
23         tag_title:
24             type: PersistedAliasMapper
25             tableName: tx_blog_domain_model_tag
26             routeFieldName: 'slug'
27         page:
28             type: StaticRangeMapper
29             start: '1'
30             end: '99'

BlogStaticDatabaseMapper

  1<?php
  2     declare(strict_types = 1);
  3
  4     /*
  5     * This file is part of the package t3g/blog.
  6     *
  7     * For the full copyright and license information, please read the
  8     * LICENSE file that was distributed with this source code.
  9     */
 10
 11     namespace T3G\AgencyPack\Blog\Routing\Aspect;
 12
 13     use TYPO3\CMS\Core\Database\ConnectionPool;
 14     use TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface;
 15     use TYPO3\CMS\Core\Utility\GeneralUtility;
 16
 17     class StaticDatabaseMapper implements StaticMappableAspectInterface, \Countable
 18     {
 19         /**
 20         * @var array
 21         */
 22         protected $settings;
 23
 24         /**
 25         * @var string
 26         */
 27         protected $field;
 28
 29         /**
 30         * @var string
 31         */
 32         protected $table;
 33
 34         /**
 35         * @var string
 36         */
 37         protected $groupBy;
 38
 39         /**
 40         * @var array
 41         */
 42         protected $where;
 43
 44         /**
 45         * @var array
 46         */
 47         protected $values;
 48
 49         /**
 50         * @param array $settings
 51         * @throws \InvalidArgumentException
 52         */
 53         public function __construct(array $settings)
 54         {
 55             $field = $settings['field'] ?? null;
 56             $table = $settings['table'] ?? null;
 57             $where = $settings['where'] ?? [];
 58             $groupBy = $settings['groupBy'] ?? '';
 59
 60             if (!is_string($field)) {
 61                 throw new \InvalidArgumentException('field must be string', 1550156808);
 62             }
 63             if (!is_string($table)) {
 64                 throw new \InvalidArgumentException('table must be string', 1550156812);
 65             }
 66             if (!is_string($groupBy)) {
 67                 throw new \InvalidArgumentException('groupBy must be string', 1550158149);
 68             }
 69             if (!is_array($where)) {
 70                 throw new \InvalidArgumentException('where must be an array', 1550157442);
 71             }
 72
 73             $this->settings = $settings;
 74             $this->field = $field;
 75             $this->table = $table;
 76             $this->where = $where;
 77             $this->groupBy = $groupBy;
 78             $this->values = $this->buildValues();
 79         }
 80
 81         /**
 82         * {@inheritdoc}
 83         */
 84         public function count(): int
 85         {
 86             return count($this->values);
 87         }
 88
 89         /**
 90         * {@inheritdoc}
 91         */
 92         public function generate(string $value): ?string
 93         {
 94             return $this->respondWhenInValues($value);
 95         }
 96
 97         /**
 98         * {@inheritdoc}
 99         */
100         public function resolve(string $value): ?string
101         {
102             return $this->respondWhenInValues($value);
103         }
104
105         /**
106         * @param string $value
107         * @return string|null
108         */
109         protected function respondWhenInValues(string $value): ?string
110         {
111             if (in_array($value, $this->values, true)) {
112                 return $value;
113             }
114             return null;
115         }
116
117         /**
118         * Builds range based on given settings and ensures each item is string.
119         * The amount of items is limited to 1000 in order to avoid brute-force
120         * scenarios and the risk of cache-flooding.
121         *
122         * In case that is not enough, creating a custom and more specific mapper
123         * is encouraged. Using high values that are not distinct exposes the site
124         * to the risk of cache-flooding.
125         *
126         * @return string[]
127         * @throws \LengthException
128         */
129         protected function buildValues(): array
130         {
131             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
132                 ->getQueryBuilderForTable($this->table);
133
134             $queryBuilder
135                 ->select($this->field)
136                 ->from($this->table);
137
138             if ($this->groupBy !== '') {
139                 $queryBuilder->groupBy($this->groupBy);
140             }
141
142             if (!empty($this->where)) {
143                 foreach ($this->where as $key => $value) {
144                     $queryBuilder->andWhere($key, $queryBuilder->createNamedParameter($value));
145                 }
146             }
147
148             return array_map('strval', array_column($queryBuilder->execute()->fetchAll(), $this->field));
149         }
150     }

Usage with imports

On typo3.com we are using imports to make routing configurations easier to manage:

1 imports:
2     - { resource: "EXT:template/Configuration/Routes/Blog/BlogCategory.yaml" }
3     - { resource: "EXT:template/Configuration/Routes/Blog/BlogTag.yaml" }
4     - { resource: "EXT:template/Configuration/Routes/Blog/BlogArchive.yaml" }
5     - { resource: "EXT:template/Configuration/Routes/Blog/BlogAuthorPosts.yaml" }
6     - { resource: "EXT:template/Configuration/Routes/Blog/BlogFeedWidget.yaml" }
7     - { resource: "EXT:template/Configuration/Routes/Blog/BlogPosts.yaml" }

Full project example config

Taken from an anonymous live project:

  1 routeEnhancers:
  2     news:
  3         type: Extbase
  4         extension: mynews
  5         plugin: mynews
  6         routes:
  7             - routePath: '/news/detail/{news}'
  8                 _controller: 'News::show'
  9                 _arguments:
 10                 news: news
 11             - routePath: '/search-result/{searchFormHash}'
 12                 _controller: 'News::list'
 13                 _arguments:
 14                     searchForm: searchForm
 15         defaultController: 'News::show'
 16         aspects:
 17             news:
 18                 routeValuePrefix: ''
 19                 type: PersistedAliasMapper
 20                 tableName: 'tx_mynews_domain_model_news'
 21                 routeFieldName: slug
 22                 valueFieldName: uid
 23     videos:
 24         type: Extbase
 25         extension: myvideos
 26         plugin: myvideos
 27         routes:
 28             -
 29                 routePath: '/video-detail/detail/{videos}'
 30                 _controller: 'Videos::show'
 31                 _arguments:
 32                     videos: videos
 33             -
 34                 routePath: '/search-result/{searchFormHash}'
 35                 _controller: 'Videos::list'
 36                 _arguments:
 37                     searchForm: searchForm
 38         defaultController: 'Videos::show'
 39         aspects:
 40             videos:
 41                 routeValuePrefix: ''
 42                 type: PersistedAliasMapper
 43                 tableName: 'tx_myvideos_domain_model_videos'
 44                 routeFieldName: slug
 45                 valueFieldName: uid
 46     discipline:
 47         type: Extbase
 48         extension: myvideos
 49         plugin: overviewlist
 50         routes:
 51            -
 52                 routePath: '/video-uebersicht/disziplin/{discipline}'
 53                 _controller: 'Overview::discipline'
 54                 _arguments:
 55                     discipline: discipline
 56         defaultController: 'Overview::discipline'
 57         aspects:
 58             discipline:
 59                 routeValuePrefix: ''
 60                 type: PersistedAliasMapper
 61                 tableName: 'tx_mytaxonomy_domain_model_discipline'
 62                 routeFieldName: slug
 63                 valueFieldName: uid
 64     events:
 65         type: Extbase
 66         extension: myapidata
 67         plugin: events
 68         routes:
 69             -
 70                 routePath: '/events/detail/{uid}'
 71                 _controller: 'Events::showByUid'
 72                 _arguments:
 73                     uid: uid
 74             -
 75                 routePath: '/events/search-result/{searchFormHash}'
 76                 _controller: 'Events::list'
 77                 _arguments:
 78                     searchForm: searchForm
 79         defaultController: 'Events::showByUid'
 80         requirements:
 81             uid: '[a-zA-Z0-9-_]+'
 82         aspects:
 83             uid:
 84                 routeValuePrefix: ''
 85                 type: PersistedAliasMapper
 86                 tableName: 'tx_myapidata_domain_model_event'
 87                 routeFieldName: slug
 88                 valueFieldName: uid
 89     results:
 90         type: Extbase
 91         extension: myapidata
 92         plugin: results
 93         routes:
 94         -
 95             routePath: '/resultset/detail/{uid}'
 96             _controller: 'Results::showByUid'
 97             _arguments:
 98                 uid: uid
 99         -
100             routePath: '/resultset/search-result/{searchFormHash}'
101             _controller: 'Results::list'
102             _arguments:
103                 searchForm: searchForm
104         defaultController: 'Results::showByUid'
105         aspects:
106         uid:
107             routeValuePrefix: ''
108             type: PersistedAliasMapper
109             tableName: 'tx_myapidata_domain_model_event'
110             routeFieldName: slug
111             valueFieldName: uid
112     teams:
113         type: Extbase
114         extension: myapidata
115         plugin: teams
116         routes:
117           -
118             routePath: '/detail/{team}'
119             _controller: 'Team::show'
120             _arguments:
121                 team: team
122           -
123             routePath: '/player/result/{searchFormHash}'
124             _controller: 'Team::list'
125             _arguments:
126                 searchForm: searchForm
127         defaultController: 'Team::show'
128         requirements:
129             team: '[a-zA-Z0-9-_]+'
130         aspects:
131             team:
132                 routeValuePrefix: ''
133                 type: PersistedAliasMapper
134                 tableName: 'tx_myapidata_domain_model_team'
135                 routeFieldName: slug
136                 valueFieldName: uid
137     moreLoads:
138         type: PageType
139         map:
140             'videos/events/videos.json': 1381404385
141             'videos/categories/videos.json': 1381404386
142             'videos/favorites/videos.json': 1381404389
143             'videos/newest/videos.json': 1381404390

EXT: DpnGlossary

Prerequisites:

  • The plugin for list view and detail view is added on one page.

  • The StaticMultiRangeMapper (a custom mapper) is available in the project.

Result:

  • List view: https://example.org/<YOUR_PLUGINPAGE_SLUG>

  • Detail view: https://example.org/<YOUR_PLUGINPAGE_SLUG>/term/the-term-title

 1routeEnhancers:
 2  DpnGlossary:
 3     type: Extbase
 4     limitToPages: [YOUR_PLUGINPAGE_UID]
 5     extension: DpnGlossary
 6     plugin: glossary
 7     routes:
 8     - { routePath: '/{character}', _controller: 'Term::list', _arguments: {'character': '@widget_0/character'} }
 9     - { routePath: '/{localized_term}/{term_name}', _controller: 'Term::show', _arguments: {'term_name': 'term'} }
10     defaultController: 'Term::list'
11     defaults:
12       character: ''
13     aspects:
14       term_name:
15         type: PersistedAliasMapper
16         tableName: 'tx_dpnglossary_domain_model_term'
17         routeFieldName: 'url_segment'
18       character:
19         type: StaticMultiRangeMapper
20         ranges:
21           - start: 'A'
22             end: 'Z'
23       localized_term:
24         type: LocaleModifier
25         default: 'term'
26         localeMap:
27         - locale: 'de_DE.*'
28           value: 'begriff'

Taken from dpn_glossary manual.