Skip to content

Filtering via routes

Learn how to filter using routes instead of parameters

The target

The standard way to filter (page) collections by tags or categories etc. is by using parameters in your URL. A typical URL usually looks like this: http://example.com/blog/tag:travel.

When a website visitor opens this link in the browser, the controller checks if the URL contains a parameter or not and returns the corresponding collection. You can find an example in the "Filtering with tags" recipe.

While this works perfectly fine, some people prefer URLs like http://example.com/blog/travel without the parameter separator or even without the parameter name, i.e. http://example.com/blog/tag/travel.

How can we achieve this? Luckily, Kirby comes with a powerful router, which we can leverage for this purpose.

Let's go through this step by step.

The route

First, we need a route that listens to our URL pattern.

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

The pattern blog/tag/(:any) with the (:any) placeholder for the actual value will listen to URLs like blog/tag/travel.

Within the action closure, we render the blog page and send the value for our tag in the methods's parameter array.

The actual filtering logic takes place in the Controller.

The controller

/site/controllers/blog.php
<?php

return function ($page, $tag) {

    $articles = $page->children()->listed();

    if ($tag) {
        $articles = $articles->filterBy('tags', $tag, ',');
    }

    return [
        'articles' => $articles
    ];

};

By loading the $tag variable to our anonymous function, we can fetch the variable we passed to the render() function in the route.

With…

if ($tag) {
  $articles = $articles->filterBy('tags', $tag, ',');
}

…we check if the $tag variable is set and if so, we filter the articles by the tag's value, and finally return the $articles variable to the template.

In your template, you can now loop through the $articles collection and render the HTML for each article.

A modified route

If you want to leave out the tag bit in the URL, you have to modify the route a bit:

/site/config/config.php
return [
    'routes' => [
        [
            'pattern' => 'blog/(:any)',
            'action' => function ($tag) {
                if ($page = page('blog/' . $tag)) {
                    return $page;
                } else {
                    return page('blog')->render([
                        'tag' => $tag
                    ]);
                }

            }
        ]
    ]
]

In this case, tag values and subpages might conflict. First we therefore check if there is a subpage and return it if it exists. If not, we handle the value as tag and render the blog page as in the route above.