This blog has been running on Kirby CMS for 6 years. And I’ve been migrating it from Kirby 2 to Kirby 3, which is taking a bit of work.

I like to do a bunch of custom things with my site’s structure and content, so instead of relying on “the Kirby way” I’m implementing a bunch of stuff myself.

That means writing PHP code that queries and manipulates data — often called “logic” code by programmers — and I’ve struggled a bit with understanding where I can put this code in the project.

Logic in templates? Sure.

One thing I like about Kirby is that it’s powerful and doesn’t require you to jump through hooks. So if you want to put all your logic in a template file, you can!

In the following example, we’re querying all the child pages of the current page, keeping the “Published” ones and sorting them by date, then listing the latest 20 pages.

<?php // site/templates/blog.php
$posts = $page->children()
  ->filterBy('isPublished', true)
  ->orderBy('date', 'desc')
  ->limit(20);
?>

<h1><?= $page->title() ?></h1>
<ul>
<?php foreach($posts as $item): ?>
  <li><?= $item->title() ?></li>
<?php endforeach; ?>
</ul>

(This template would be used for any page which has the 'blog' content type, meaning its content file is something like content/my-page/blog.txt. See “Creating pages” in the Kirby documentation for more information.)

Or in controllers, maybe?

Putting logic in template is great for people just getting started with Kirby, or people who may not be professional programmers. But if you want to separate your logic from the template, you can move it to a controller function:

<?php // site/controllers/blog.php

return function ($page) {
  $posts = $page->children()
    ->filterBy('isPublished', true)
    ->orderBy('date', 'desc')
    ->limit(20);
  return ['posts' => $posts];
};

But what if you want to share some logic between different templates, controllers or page types? It would be awesome if you could call something like $page->getPublishedChildren(), from anywhere.

Kirby offers two ways to do that:

  1. Page Methods
  2. Page Models

Creating a page method

Page methods apply to all $page objects, regardless of page type. They’re a slightly older technique in Kirby, and in Kirby 3 you need to declare a plugin to add page methods:

<?php // site/plugins/my-plugin/index.php

Kirby::plugin('me/my-plugin', [
  'pageMethods' => [
    'getPublishedChildren' => function() {
      return $this->children()
        ->filterBy('isPublished', true)
        ->orderBy('date', 'desc')
        ->limit(20);
    }
  ]
]);

There are a few downsides to page methods:

  1. Having to register a plugin is a bit awkward.
  2. If you’re using PHP types and intellisense in your editor, your editor has no way to know that $this in your page method is an instance of Kirby’s Page class. That means you don’t get code completions and validation in your editor.

Creating a page model

The newer option is to use page models, which are defined as PHP classes extending Kirby’s Page class.

<?php // site/models/blog.php

class BlogPage extends Page {
  public function getPublishedChildren() {
    return $this->children()
      ->filterBy('isPublished', true)
      ->orderBy('date', 'desc')
      ->limit(20);
  }
}

This looks a little bit cleaner to me, mainly because we avoid creating a plugin. And since we’re extending the Page class, now our code editors will know what methods the $this object has1.

And reading the Kirby documentation, and especially the Guide, it looks like Page Models are the preferred mechanism for adding functionality to page objects.

But they have one big downside: they only apply to a given page type, the 'blog' page type in this example. If you try to use $page->getPublishedChildren() on a page with a different type, you get an error.

Are we forced to use Page Methods in a plugin to share functionality between pages? Well, maybe not.

Creating a default page model

Our page models already inherit from Kirby’s Page class. What if we added one more class to that hierarchy?

Let’s start by moving our method to a new class, which we’ll call DefaultPage. Ideally we should pick a name that doesn’t match a page type (meaning we don’t have pages with a content file named default.txt).

<?php // site/models/default.php

class DefaultPage extends Page {
  public function getPublishedChildren() {
    /* ... */
  }
}

Then we can create a class for our 'blog' page type, which extends DefaultPage:

<?php // site/models/blog.php
require_once 'default.php';

class BlogPage extends DefaultPage {
  // Add methods specific to the 'blog' type
}

Note that we have to require default.php explicitly to load the DefaultPage class, because Kirby won’t load it automatically since it doesn’t match a real page type.

If we add a new page type, we can create an empty page model that inherits from DefaultPage, and it will inherit the same shared methods.

<?php // site/models/article.php
require_once 'default.php';

class ArticlePage extends DefaultPage {}

Fair warning: this means that to fully emulate a global “page method”, we have to create a page model for every single page type we have!

If that’s too verbose, your options are:

Happy coding!


  1. At least if we’re using a code editor with good support for PHP, like PhpStorm; in my experience VS Code was too limited out of the box, and I couldn’t figure out which plugins to try to improve that.