Skip to content

Routing

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 https://yourdomain.com/blog/my-super-post),
  • 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.

Defining your own routes

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: pattern and action.

In your config

/site/config/config.php
return [
  'routes' => [
    [
      'pattern' => 'my/awesome/url',
      'action'  => function () {
        // do something here
        // when the URL matches the pattern above
      }
    ],
    [
      'pattern' => 'my/second/url',
      'action'  => function () {
        // ...
      }
    ]
  ]
];

In a plugin

Kirby::plugin('your/plugin', [
  'routes' => [
    [
      'pattern' => 'my/awesome/url',
      'action'  => function () {
        // do something here
        // when the URL matches the pattern above
      }
    ]
  ]
]);

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):

Kirby::plugin('your/plugin', [
  'routes' => function ($kirby) {
      return [
          [
              'pattern' => 'something/' . $kirby->option('something'),
              'action' => function () {
                // ...
              }
          ]
      ];
  }
]);

Patterns

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:

[
  'pattern' => 'my/awesome/url/(:any)/(:num)/(:all)',
  'action'  => function($any, $num, $all) {
    // ...
  }
]

If you want to use the same action for multiple patterns, you can either use regex expressions or pass an array of patterns:

[
  'pattern' => ['blog/(:any)', 'notes/(:any)'],
  'action'  => function() {
    // ...
  }
]

Response types

Depending on your use case, routes can return various response types, which will be handled accordingly by Kirby:

Page object

[
  'pattern' => 'portfolio',
  'action'  => function () {
    return page('projects');
  }
]

File object

[
  'pattern' => 'load/(:any)',
  'action'  => function ($name) {
    return page('downloads')->file($name);
  }
]

String: HTML

[
  'pattern' => 'boring.html',
  'action'  => function () {
    return '<html><body>Boring!</body></html>';
  }
]

Not found: false, null or ''

[
  'pattern' => 'not/found',
  'action'  => function () {
    return false;
  }
]

JSON from array

[
  'pattern' => 'fancy.json',
  'action'  => function () {
    return [
      'status' => 'ok',
      'data'   => ['foo' => 'bar']
    ];
  }
]

Response object

[
  'pattern' => 'custom/response',
  'action'  => function () {
    return new Response('<foo>bar</foo>', 'text/html');
  }
]

Exception

[
  'pattern' => 'custom/response',
  'action'  => function () {
    throw new Exception('Something went horribly wrong');
  }
]

Methods

By default routes are only available for GET requests. You can define additional request methods for the route:

  • CONNECT
  • DELETE
  • GET
  • HEAD
  • OPTIONS
  • PATCH
  • POST
  • PUT
  • TRACE
[
  'pattern' => 'my/awesome/url',
  'action'  => function () {
    // ..
  },
  'method' => 'GET|POST|DELETE'
]

Multi-language setup

In case of multi-language sites you must call the $site->visit() method in order to activate the selected page and set a language.

[
  'pattern' => 'custom/response',
  'action'  => function () {
      return site()->visit('some/page', 'en');
  }
]

next()

Since 3.0.3

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.

/site/config/config.php
return [
    'routes' => [
        [
            'pattern' => '(:any)',
            'action'  => function ($slug) {
                if ($page = page('photography')->find($slug)) {
                    return page($page);
                }

                $this->next();
            }
        ]
    ]
];

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 $this->next().

Before and after hooks

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.

route:before

/site/config/config.php
return [
  'hooks' => [
    'route:before' => function ($route, $path, $method) {
      if ($path === 'super/secret' && !kirby()->user()) {
        die('Nope');
      }
    }
  ]
];

route:after

/site/config/config.php
return [
  'hooks' => [
    'route:after' => function ($route, $path, $method, $result) {
      return myHtmlCompressor($result);
    }
  ]
];

Virtual Pages

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.

/site/config/config.php
return [
  'routes' => [
    [
      'pattern' => 'virtual-reality',
      'action'  => function () {
        return new Page([
          'slug' => 'virtual-reality',
          'template' => 'virtual-page',
          'content' => [
            'title' => 'This is not a real page',
            'text'  => 'Believe it or not, this page is not in the file system'
          ]
        ]);
      }
    ]
  ]
];