Optional route pattern

We have a calendar with an URL scheme like /page/year/month/day, e. g. /calendar/2020/02/26. The year, month and day are kind of virtual pages: everything is handled by the calendar page. We have this route to get the actual date:

'routes' => [
        'pattern' => '(:any)/(:num)/(:num)/(:num)',
        'action' => function ($page, $year, $month, $day) {
            return page($page)->render([
                'year' => $year,
                'month' => $month,
                'day' => $day

The thing is, the year, month and day parameters are supposed to be optional and be set to default values if non-existent.

Is there a way to mark the (:num) patterns as optional so we don’t have to create separate routes for calendar/2020/02/26, calendar/2020/02, calendar/2020 and calendar?

This should work:

'pattern' => '(:any)/(:num?)/(:num?)/(:num?)',

That works indeed!
Is that in the docs somewhere? I couldn’t find it.

Nope, that’s a standard regex pattern.

Wouldn’t that imply that I could do ([a-z]:num?) which is not possible if I remember correctly?
Could be helpful to add a note about this.

No, that’s not supported, because of this:

  protected $wildcards = [
        'required' => [
            '(:num)'      => '(-?[0-9]+)',
            '(:alpha)'    => '([a-zA-Z]+)',
            '(:alphanum)' => '([a-zA-Z0-9]+)',
            '(:any)'      => '([a-zA-Z0-9\.\-_%= \+\@\(\)]+)',
            '(:all)'      => '(.*)',
        'optional' => [
            '/(:num?)'      => '(?:/(-?[0-9]+)',
            '/(:alpha?)'    => '(?:/([a-zA-Z]+)',
            '/(:alphanum?)' => '(?:/([a-zA-Z0-9]+)',
            '/(:any?)'      => '(?:/([a-zA-Z0-9\.\-_%= \+\@\(\)]+)',
            '/(:all?)'      => '(?:/(.*)',

So, I was wrong and these optional wildcards are in fact a Kirby thing but not documented (and yes, we should in fact do that).

That would be great, thanks! At least I tend to forget these things again :roll_eyes:

Another thing that I didn’t understand when reading the docs was that the slashes in the pattern are ignored, if they are not present in the actual URL. E. g. I didn’t expect that the pattern (:any)/(:num?)/(:num?)/(:num?) would match calendar/2020. So I was thinking to complicated when trying so solve this problem because I was expecting stricter matching.

With the optional pattern, I’m getting this error:

Too few arguments to function Kirby\Http..., 0 passed and exactly 1 expected error.

    // map plugin index.php
    'routes' => [
        'pattern' => 'mapajax.json/category/(:any?)',
        'action'  => function ($category) {
            if(!isset($category) {

I’m running Kirby 3.5.0.

@lukehatfield Would be useful to have all the information…

sorry, is this enough? maybe it’s different b/c it’s in a plugin?

// site/plugins/kirby-map/index.php

namespace visualdialogue\Map;

\Kirby::plugin('visualdialogue/map', [

    'routes' => [
        'pattern' => 'mapajax.json/category/(:any?)',
        'action'  => function ($category) {

          if(!isset($category)) {
            $category = 'all';

          // virtual page so can get json content
          return \Page::factory([
            'slug' => 'mapajax.json',
            'template' => 'mapajax.json',
            'category' => $category

and here’s the error when I try to access the route without passing the parameter…

The problem is that the $category parameter you pass to the closure doesn’t exists if there is no value for (:any?).

So the route should look like this:

'pattern' => 'test/category/(:any?)',
'action'  => function () {

and you would have to get the value via $this->arguments(), which will return an empty array if the optional argument doesn’t exist, otherwise contain the arguments.

1 Like