Skip to content

Kirby 3.9.8

Filtering compendium

This guide will give you an overview about the many ways you can filter collections of pages, files, users, structures, languages or general collections in Kirby.

Try to avoid filtering the complete site index ($site->index()), especially if your site has thousands of pages and files. Always start with the smallest "base" collection possible and use caching.

For often used collections, check out Kirby collections.

Filtering by listed/unlisted

In Kirby, pages can be either listed (folder with prepended number/date) or unlisted. Kirby has two handy built-in methods to filter pages according to their status:

$collection = page('projects')->children()->listed();
$collection = page('projects')->children()->unlisted();

Filter page collections by a single field

Often, you only want to display all pages that have a certain value in a special field, e.g. a category or a tags field. For this task, you can use the filterBy() method:

// filter a collection by a value in a single value field
$collection = page('projects')
  ->children()
  ->filterBy('category', 'webdesign');

// filter a collection by a value in a field with a comma separated list of values
$collection = page('projects')
  ->children()
  ->filterBy('tags', 'webdesign', ',');

Note the use of the delimiter in the second example to get a value from a comma separated list. If you use other delimiters in your fields, you can, of course, change the delimiter.

Filtering using filter operators

With the above example, you only get results that fit a single value. You can fine tune your filter results by using the operator parameter in the filterBy() method:

Pages

// get all pages with a date after now
$collection = page('blog')
  ->children()
  ->filter(function ($child) {
    return $child->date()->toDate() > time();
  });

// get all pages with a title that starts with "A" or a number
$collection = page('blog')
  ->children()
  ->filterBy('title', '<', 'B');

To fetch all sibling pages that share one or more tags stored in a comma separated list, you can use the in filter operator like this:

$relatedSiblings = $page->siblings(false)->filterBy('tags', 'in', $page->tags()->split(','), ',');

To fetch related pages that have at least two tags in common, we can use the filter() method instead:

$relatedSiblings = $page->siblings(false)->filter(function($sibling) use ($page) {
  return count(array_intersect($sibling->tags()->split(','), $page->tags()->split(','))) >= 2;
});
// get all images that contain a string
$images = page('blog')
  ->images()
  ->filterBy('filename', '*=', 'cover-');

// get all file types except images
$files = page('blog')
  ->files()
  ->filterBy('type', '!=', 'image');

Available filter methods

Method Function
== all values that match exactly
!= all values that don't match
in takes an array as parameter, matches all values that are included in the array
not in takes an array as parameter, matches all values that are not included in the array
> all values that are greater than the given value
>= all values that are greater or equal the given value
< all values that are smaller than the given value
<= all values that are smaller or equal the given value
*= all values that contain the given string
!*= all values that don't contain the given string
^= all values that start with the given string
!^= all values that don't start with the given string
$= all values that end with the given string
!$= all values that don't end with the given string
* all values that match the given regular expression
!* all values that don't match the given regular expression
between or .. takes an array as parameter with two parameters; first is the min value, second is the max value
maxlength all values that have the given maximum length
minlength all values that have the given minimum length
maxwords all values that have the given maximum amount of words
minwords all values that have the given minimum amount of words
date == (added in 3.4.0) all date values that exactly match the given date string
date != (added in 3.4.0) all date values that don't match the given date string
date > (added in 3.4.0) all date values that are later than the given date string
date >= (added in 3.4.0) all date values that are later or equal the given date string
date < (added in 3.4.0) all date values that are earlier than the given date string
date <= (added in 3.4.0) all date values that are earlier or equal the given date string
date between or date .. (added in 3.4.0) all date values that are between the given date strings

Filtering by methods

You cannot only filter by custom field methods in your content files but also by methods of a pages', files', users' collection. Here are some examples:

Pages

// filter by page template
$collection = page('projects')
  ->children()
  ->filterBy('template', 'article');

// filter by intended template
$collection = page('projects')
  ->children()
  ->filterBy('intendedTemplate', 'article');

// filter by depth
$collection = $site
  ->index()
  ->filterBy('depth', 2);

// filter by modified date
$collection = $site
  ->index()
  ->filterBy('modified', '>', strtotime('2018-04-30'));

Files

// filter by file extension
$images = $page
  ->images()
  ->filterBy('extension', 'png');

// filter by type
$files = $page
  ->files()
  ->filterBy('type', 'document');

Users

// filter by role
$users = $kirby
  ->users()
  ->filterBy('role', 'editor');

Filter collections by more than one field/method

You can also string together multiple filterBy() methods to narrow down your filter results (using AND logic):

Pages

// fetch all events with a template that is neither "concert" nor "exhibition"
$collection = page('events')
  ->children()
  ->filterBy('template', '!=', 'concert')
  ->filterBy('template', '!=', 'exhibition');

You can achieve the same with the filter($callback) method:

$collection = page('events')
  ->children()
  ->filter(function ($p) {
    return $p->template() != 'concert' && $p->template() != 'exhibition';
  });

Or you can use the not in filter:

// fetch all events with a template that is neither "concert" nor "exhibition"
$collection = page('events')
  ->children()
  ->filterBy('template', 'not in', ['concert', 'exhibition']);

Files

// fetch all files that are neither videos nor images
$files = $page
  ->files()
  ->filterBy('type', '!=', 'video')
  ->filterBy('type', '!=', 'image');

Users

// fetch all users that are neither admins nor editors
$files = $kirby
  ->users()
  ->filterBy('role', '!=', 'admin')
  ->filterBy('role', '!=', 'editor');

Fun with filtering by date

Filtering from - to

The date ... filters were added in Kirby 3.4.0.

For a simple from - to filtering of dates, you can string together two filterBy() methods as seen above:

// fetch all blog post of 2018 using beginning and end dates
$collection = page('blog')
  ->children()
  ->filterBy('date', 'date >', '2018-01-01')
  ->filterBy('date', 'date <', '2018-12-31');

If you have a date field but want to filter by year, you can use a filter with a callback:

$year     = date('Y');
$articles = page('blog')
  ->children()
  ->filter(function ($page) use ($year) {
    return $page->date()->toDate('Y') === $year;
  });

Multiple date fields

Events often have startdate and enddate date fields. To filter events that either end or begin in the future, you can use a filter with a callback:

$events = page('events')
  ->children()
  ->filter(function ($child) {
    return $child->startdate()->toDate() > time() || $child->enddate()->toDate() > time();
  });

With two little changes to the above code, you can fetch all current events, that is events that have already started but not ended yet:

$events = page('events')
  ->children()
  ->filter(function ($child) {
    return $child->startdate()->toDate() > time() && $child->enddate()->toDate() < time();
  });

Filtering by the pages, files or users field

The pages, files and users fields store their content in yaml format. Also, these fields can contain one or multiple entries. Using the simple filterby() method is therefore not useful to filter a collection by these field types and we have to use the filter() method.

Let's first understand what these fields store in the content files:

  • The pages field stores a single or multiple page ids
  • The files field stores a single or multiple file ids
  • The users field stores a single or multiple user emails

Now We can use this information to filter pages or other collections by content stored in these fields.

Examples

Filter by pages field

For example, fetch all pages in a collection that reference the current page (or any other page) in the related field:

$page->children()->filter(function($child) use($page) {
    return $child->related()->toPages()->has($page);
});

Filter by files field

For example, fetch all pages in a collection that reference a given file in the pictures field:

$file = $page->images()->first();
$page->children()->filter(function($child) use($file){
    return $child->pictures()->toFiles()->has($file);
});

Filter by users field

For example, fetch all pages in a collection that reference the current user in the authorsfield:

$user = $kirby->user();
$page->children()->filter(function($child) use($user) {
    return $child->authors()->toUsers()->has($user);
});

While these examples showcase pages collections, the same syntax can be applied to filtering files or users that contain any of these fields.

Filtering using the search method

Another option of filtering collections is by using the search() method. There is another cookbook recipe on how to add a search to your site.