# Routes

(screencast: https://youtu.be/5u7IF2j8UZ0 text: Use the built-in router to create custom routes for redirects, stable URLs, or even virtual pages.)

****

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 redirect outdated links, or 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

```php "/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

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

```php
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 <a href="https://getkirby.com/docs/reference/router/patterns">dynamic placeholders</a>:

<nav class="auto-fill mb-12 text-sm" style="--min: 20rem; --gap: var(--spacing-1)">
		<a class="block p-3 bg-light" href="https://getkirby.com/docs/reference/router/patterns/all" style="text-decoration: none">
		<strong class="block">(:all)</strong>
		Matches all characters including slashes	</a>
		<a class="block p-3 bg-light" href="https://getkirby.com/docs/reference/router/patterns/alpha" style="text-decoration: none">
		<strong class="block">(:alpha)</strong>
		Matches any characters between a-z &amp; A-Z	</a>
		<a class="block p-3 bg-light" href="https://getkirby.com/docs/reference/router/patterns/alphanum" style="text-decoration: none">
		<strong class="block">(:alphanum)</strong>
		Matches any characters between a-z, A-Z &amp; 0-9	</a>
		<a class="block p-3 bg-light" href="https://getkirby.com/docs/reference/router/patterns/any" style="text-decoration: none">
		<strong class="block">(:any)</strong>
		Matches any character until the next slash	</a>
		<a class="block p-3 bg-light" href="https://getkirby.com/docs/reference/router/patterns/num" style="text-decoration: none">
		<strong class="block">(:num)</strong>
		Matches any number	</a>
	</nav>

Placeholders can also contain expressions, e.g. `([a-z]+)`, and will be passed as arguments to the callback function in the order they appear:

```php
[
  '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:

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

<warning>
When using placeholders like `(:any)` and `(: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 `notes` or `photography`, but also their JSON content representations `notes.json` or `photography.json`. In this case, using `(:alphanum)` can be more suitable.
</warning>

## Response types

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

### Page object

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

### File object

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

### String: HTML

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

### Not found: `false`, `null` or `''`

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

### JSON from array

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

### Response object

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

```php
[
  '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`

```php
[
  'pattern' => 'my/awesome/url',
  'action'  => function () {
    // ..
  },
  'method' => 'GET|POST|DELETE'
]
```
## Simple redirects with `go()`

This is probably one of the most used use-cases for routes, a redirect from a path in your Kirby project to some other location.

```php
return [
    'routes' => [
        [
            'pattern' => 'some-pattern',
            'action' => function () {

                return go('https://example.com');
            }
        ],
    ]
];
```
This pattern will listen to `my.domain/some-pattern` and redirect to the new location, in this example an external URL `https://example.com`.

Here is an example to redirect from the path of a non-existing-page in your Kirby project to a new location:

```php
return [
    'routes' => [
        [
            'pattern' => 'notes/does-not-exist-anymore',
            'action' => function () {

                return go('notes/does-exist');
            }
        ],
    ]
];
```

Note that the <a href="https://getkirby.com/docs/reference/templates/helpers/go">`go()`</a> helper accepts a HTTP response code as a second argument, the default is `302`. You might prefer a `301` is the redirect is permanent.

## 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.

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

### Language scope

In multi-lang installations, you can set a language scope for your routes:

```php
return [
    'routes' => [
        [
            'pattern' => 'test',
            'language' => 'en',
            'action' => function ($language, $slug) {

                if (page($slug)) {
                    return $this->next();
                }

                if ($page = page('notes/' . $slug)) {
                    return $page;
                }

                return false;

            }
        ],
    ]
];
```

The above route will respond to a pattern like `example.com/test` (if `en` is the default language) or `example.com/en/test`. If `en` is **not** the default language, the route will only respond to `example.com/en/test`.

If you call the above language-scoped route pattern using another language like `example.com/de/test`, the above route will not respond and show the error page instead.

<info>
In a multi-language context, routes are supposed to be called **with** the language code in the URL (unless it is the default language). Otherwise, `$language` will always refer to the default language.
</info>

If you don't set the language scope in a multi-language context, the above route will only respond to `example.com/test`, not to URLs that contain a language code.

To make a route respond to all languages, you can set the language scope property to `*`.

```php
return [
    'routes' => [
        [
            'pattern' => 'test',
            'language' => '*',
            'action' => function ($language, $slug) {

                if (page($slug)) {
                    return $this->next();
                }

                if ($page = page('notes/' . $slug)) {
                    return $page;
                }

                return false;

            }
        ],
    ]
];
```

This route pattern will respond to `example.com/en/test`, `example.com/de/test` or whatever languages you have defined.

### Page scope

To make sure that translated slugs are automatically handled by Kirby, you can set a page scope:

```php
return [
    'routes' => [
        [
            'pattern' => 'tag/(:any)',
            'language' => '*',
            'page' => 'notes',
            'action' => function ($language, $page, $filter) {
              return $page->render([
                'filter' => $filter
              ]);
            }
        ],
    ]
];
```
In this example, we return the `notes` page with the filter data. In your controller, you can now <a href="https://getkirby.com/docs/guide/templates/controllers#arguments-from-page-render-in-route">use this filter to return only the children with the given tag</a>.

Since we have set the language scope to `*`, this route will listen to all languages.

## `next()`

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 <a class="type-link" href="https://getkirby.com/docs/reference/objects/http/route/next"><code class="type type-method">$this-&gt;next()</code></a> call within your route.

```php "/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 <a class="type-link" href="https://getkirby.com/docs/reference/objects/http/route/next"><code class="type type-method">$this-&gt;next()</code></a>.

## Pass data to controller

You can send additional data to the controller using the `$page->render()` method:

```php "/site/config/config.php"
<?php
return [
    'routes' => [
        [
            'pattern' => 'blog/tag/(:any)',
            'action' => function ($value) {
                $data = [
                  'tag' => $value,
                ];
                return page('blog')->render($data);
            }
        ]
    ]
];
```

You can then <a href="https://getkirby.com/docs/guide/templates/controllers#arguments-from-page-render-in-route">fetch this data in the controller</a>.

## 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

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

### route:after

```php "/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.

```php "/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'
          ]
        ]);
      }
    ]
  ]
];
```

<warning>
In this example, we have hardcoded the properties of the `Page` object. This can be useful for fixed pages you don't want to manage via the content folder. However once you start introducing dynamic and especially external data, please ensure that the data is clean to ensure reliability and prevent attacks. With great power comes great responsibility. You can read more about this in our <a href="https://getkirby.com/docs/guide/security#filter-and-sanitize-inputs__filter-and-sanitize-properties-of-virtual-content">security guide</a>.
</warning>

## Removing the parent page slug from the URL

Sometimes we want to get rid of part of an URL for cleaner URLs, for example to replace the following URL

```
http://yourdomain.com/blog/my-awesome-article
```

…with…
```

http://yourdomain.com/my-awesome-article
````

This can be achieved with the following two routes in your `config.php` (or in a plugin):

```php "/site/config/config.php"
<?php

return [
    'routes' => [
        [
            'pattern' => '(:any)',
            'action'  => function($uid) {
                $page = page($uid);
                if (!$page) $page = page('blog/' . $uid);
                if (!$page) $page = site()->errorPage();
                return site()->visit($page);
            }
        ],
        [
            'pattern' => 'blog/(:any)',
            'action'  => function($uid) {
                go($uid);
            }
        ]
    ]
];
```
Replace `blog` with the slug of the parent page you want to remove from the URL.

Additionally, you can overwrite the `$page->url()` in a <a href="https://getkirby.com/docs/guide/templates/page-models">page model</a> to return the desired target URL when you use this method.

## Accessing parameters and query strings

Query strings and parameters cannot be part of a route pattern. However, you can get passed parameters or query strings with Kirby's `param()` and `get()` helpers inside a route:

```php "/site/config/config.php"
<?php

return [
    'routes' => [
        [
            'pattern' => 'products/(:any)',
            'action'  => function($uid) {
                // parameter, e.g. `https://example.com/products/color:black`
                if ($param = param('color')) {
                  // do something
                }
                // query string, e.g. `https://example.com/products?color=black`
                if ($query = get('color')) {
                  // do something
                }
                // ...
            }
        ],
    ]
];
```

## More information

<a href="https://getkirby.com/docs/cookbook/collections/filter-via-route">Filtering via routes</a>