When you create a new page in Kirby's content folder, this folder creates a new URL, for example, the page
/content/projects/project-a will be available at
https://yourdomain.com/projects/project-a. So when the user types that URL into the browser's address bar, the project page will be rendered.
Sometimes however, you may want to provide content under URLs that do not exist as pages in your content folder, for example:
- to create virtual utility pages like a sitemap or an RSS feed,
- to redirect to other parts of your installation or to remove parts of a page URL to make pages available at a shorter URL (for example, leave out the blog folder when accessing posts that would otherwise be available at
- to create custom API endpoints,
- to return a response object,
- to filter by URL rather than using parameters,
That is where routes come into play. Routes react on an URL pattern. They can either return something to users when they visit an URL with that pattern, or they can have functional purposes, e.g. some function is executed when a script calls a particular URL.
Kirby has a built-in router, which can be extended with your own routes. Routes can be setup with the
routes option in your config or in plugins. Routes are simple associative arrays with two required fields:
You can also define your route definition as a callback, which gives you more control, e.g. to access information from inside Kirby in your patterns (in this example to use a Kirby option as part of the pattern):
In your route patterns, you can use static, relative URL strings, e.g.
some/static/url, but also dynamic placeholders:
Placeholders can also contain expressions, e.g.
([a-z]+), and will be passed as arguments to the callback function in the order they appear:
If you want to use the same action for multiple patterns, you can either use regex expressions or pass an array of patterns:
When using placeholders like
(:all) or regex patterns, make sure that they are not too greedy and you accidentally catch routes you want left intact, resulting in things not working as expected anymore.
Example: A pattern like
(:any) will not only catch routes with a URL part like
photography, but also their JSON content representations
photography.json. In this case, using
(:alphanum) can be more suitable.
Depending on your use case, routes can return various response types, which will be handled accordingly by Kirby:
By default routes are only available for
GET requests. You can define additional request methods for the route:
In case of multi-language sites you must call the
$site->visit() method in order to activate the selected page and set a language.
In multi-lang installations, you can set a language scope for the route:
To make sure that translated slugs are automatically handled by Kirby, you can set a page scope:
In this example, we return the
notes page with the filter data. In your controller, you can now use this filter to return only the children with the given tag.
In some scenarios, you want to perform actions on all requests matching a route pattern, but then let the router find the next matching route. For this scenario you can use the
$this->next() call within your route.
This is perfect for the example above when you want to intercept URLs like
https://yourdomain.com/photography-project-name and create flat URLs. But at the same time you don’t want to break all the other pages. In this case you can search for the photography project in the route and if it doesn’t exist you can jump to the regular route for all the other pages with
You can send additional data to the controller using the
You can then fetch this data in the controller.
You can register hooks for the event when a route has been found, but has not been executed yet, and when the route has just been executed. Those hooks are very useful to intercept certain routes based on your own rules, or to manipulate the result of a particular route.
A route can return a virtual page that doesn't really exist in the file system. This is very useful to mock pages with custom data from other data sources.
Sometimes we want to get rid of part of an URL for cleaner URLs, for example to replace the following URL
This can be achieved with the following two routes in your
config.php (or in a plugin):
blog with the slug of the parent page you want to remove from the URL.