Templates should contain as little code as possible to separate logic and form. With template controllers you can add more logic to your templates without messing up your markup.

The controllers directory

Controllers go into /site/controllers. Please create the directory if it's not there yet. Controllers are named exactly like the templates they belong to:

Template Controller
/site/templates/article.php /site/controllers/article.php

Kirby will automatically load the controller for your template if it can find one.

2.3.0 +

Default controller

You can also have a default page controller: It works like a normal page controller. You only have to add it as site/controllers/site.php. Be aware that the default controller only works as a fallback if no specific page controller for the template exists. It is not loaded in addition to the template-specific one.

Creating a controller

The basic code for a controller is very simple. It's an anonymous function, which receives the $site, $pages and $page variables as arguments and should return any number of variables as associative array, which you want to pass to the template.


return function($site, $pages, $page) {
  return array(
    'foo' => 'my first template variable',
    'bar' => 'my second template variable'

In this case the $foo and $bar variables will be available in the corresponding template.

A real world example

A typical example when a controller can come in handy is a blog template. Fetching the right articles can take a couple lines of code and it's much nicer to wrap this in a controller. So let's create /site/templates/blog.php and /site/controllers/blog.php

The controller

The controller will take care of fetching the right articles and add pagination to them. It will then pass an $articles variable to the template to keep it nice and clean.


return function($site, $pages, $page) {

  // get all articles
  $articles = $page->children()->visible()->flip();

  // add a tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', '=', urldecode($tag), ',');

  // add pagination
  $articles = $articles->paginate(20);

  // create a shortcut for pagination
  $pagination = $articles->pagination();

  // pass $articles and $pagination to the template
  return compact('articles', 'pagination');


The template

With such a controller, the template can be super clean. A foreach loop for the articles and the prev/next pagination and you're done.

<?php snippet('header') ?>

<?php foreach($articles as $article): ?>
<!-- the code for each article goes here -->
<?php endforeach ?>

<nav class="pagination">

  <?php if($pagination->hasPrevPage()): ?>
  <a href="<?= $pagination->prevPageUrl() ?>">previous articles</a>
  <?php endif ?>

  <?php if($pagination->hasNextPage()): ?>
  <a href="<?= $pagination->nextPageUrl() ?>">next articles</a>
  <?php endif ?>


<?php snippet('footer') ?>

Arguments from the router

If you are working with additional routes and you want to access the arguments from the router in your controller you can use the fourth argument to access an array of router arguments.


return function($site, $pages, $page, $args) {

  // $args contains all arguments from the current route