Skip to content

Kirby 3.5.7.1

Generating JSON with Kirby

With Kirby, you cannot only generate HTML output. In this recipe, we will explain how to generate JSON with Kirby using content representations. If you've never heard about JSON, this tutorial is probably not for you, but you should definitely learn more about it – it's awesome!

Kirby's template system does not really care what you build with it. In fact you could even generate an Excel spreadsheet with the data of your site – though nobody wants that :)

Content representations allow you to output the content in different formats. Be it JSON for your AJAX script or to use Kirby as API for other tools, an automatic RSS feed representation of your blog or a plain text representation of your résumé.

For this tutorial we are going to build a little JSON content representation for our blog (alongside your HTML blog template), which will provide a list of articles as JSON string. You could use that to add endless scrolling for your blog for example, or to load blog articles dynamically in an app or some other fancy way.

Content structure

The content structure for the blog will be something like this:

  • content
    • blog
      • 1_my-first-article
        • article.txt
      • 2_my-second-article
      • 3_my-third-article
      • blog.txt

Please read the article about how to build a blog with Kirby, if this seems weird to you.

Content representation for the blog

In this example we will create a JSON representation of the blog template. After we are done, our blog page will not only be able to viewed as HTML but also be available in JSON. We will see below how exactly that works.

A representation is defined by its representation template. For our JSON representation, let's create a file blog.json.php in our /site/templates directory.

  • /site/templates/blog.json.php

Create the template

Open the new template file and add the following code:

/site/templates/blog.json.php
<?php

$data = $pages->find('blog')->children()->published()->flip();
$json = [];

foreach($data as $article) {

  $json[] = [
    'url'   => (string)$article->url(),
    'title' => (string)$article->title(),
    'text'  => (string)$article->text(),
    'date'  => (string)$article->date()
  ];

}

echo json_encode($json);

It might look a bit frightening first, but let me explain this a bit more.

1. Fetching the data

$data = $pages->find('blog')->children()->published()->flip();

What we want is to fetch the same list of articles, which we use to build our regular blog template. First we are going to find the blog page -> then we select all children -> make sure to get the published children only -> and finally flip them to get the latest article first.

2. Building the result

$json = [];

foreach($data as $article) {

  $json[] = [
    'url'   => (string)$article->url(),
    'title' => (string)$article->title(),
    'text'  => (string)$article->text(),
    'date'  => (string)$article->date()
  ];

}

Once we've got our list of articles, we should build a nice array out of it, which we can use to generate JSON. The main goal of JSON is to reduce the amount of data, which is transfered between the client and the server, so you should only go on with the data you really need. In this case I create one big array of arrays ($json), with an nested array for each article.

There's one little thing you need to consider, when building that array. All the custom variables (title, text, date, etc.) are not just simple strings in Kirby, but objects. It's beyond the scope of this article to explain why, but what you need to do is to make sure those objects will be converted to strings by using (string):

(string)$article->title()

3. Returning JSON

Once your $json array is ready, you need to convert this to a json string with the native PHP json_encode function.

echo json_encode($json);

This will echo the entire array as a JSON encoded string. The template does not generate any HTML at all.

You don't need to manually send a content type header as Kirby will do that for you based on the representation type. Here, the response will automatically be sent with the content type application/json.

Using your JSON representation

You can now access the JSON of your blog with javascript or however you plan to use it. I'm using plain javascript here to make a little example call. I guess it will be the most common tool to do this.

Example call

fetch('/blog.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(articles) {
    console.log(JSON.stringify(articles));
  });

Using content representations, the URL for the JSON will become https://yourdomain.com/blog.json.

Securing your JSON

If you want to avoid that someone else is accessing the JSON, you can add a custom header to your request which may then be validated by the server. In contrast to the superseded XMLHttpRequest(), fetch() doesn't set a X-Requested-With header by default. We simply add it ourself.

First, modify your JavaScript code to pass along the header with a body of your choice:

fetch('/blog.json', {
  method: 'GET',
  headers: {
    'X-Requested-With': 'fetch'
  }
})
  .then(function(response) {
    return response.json();
  })
  .then(function(articles) {
    console.log(JSON.stringify(articles));
  });

Since the custom header alone won't secure anything, we will have to create a route for JSON requests, which validates the header's body.

/site/config/config.php
[
  'pattern' => '(:any).json',
  'action'  => function () {
    $customHeader = $_SERVER['HTTP_X_REQUESTED_WITH'] ?? null;

    // Secure JSON output from direct access in production environment
    if (option('debug') === false && $customHeader !== 'fetch') {
      go(url('error'));
    }

    $this->next();
  }
]

Because it is a convenient way to test the generated JSON directly in the browser via the URL, we only validate the requests in production mode.

Now whenever a regular request is coming in and the X-Requested-With doesn't match, go(url('error')) will be called, which redirects the visitor to the error page.

Pagination

Depending on the size of your blog, you migh want to add pagination to your JSON response.

/site/templates/blog.json.php
<?php

$data = $pages->find('blog')->children()->published()->paginate(10);
$json = [];

$json['data']  = [];
$json['pages'] = $data->pagination()->pages();
$json['page']  = $data->pagination()->page();

foreach($data as $article) {

  $json['data'][] = array(
    'url'   => (string)$article->url(),
    'title' => (string)$article->title(),
    'text'  => (string)$article->text(),
    'date'  => (string)$article->date()
  );

}

Since we added ->paginate(10) to the definition of our pages collection, you can now easily jump to different pages like this:

fetch('/blog.json/page:2')
…

fetch('/blog.json/page:3')
…

fetch('/blog.json/page:4')
…

The entire code

Here is the entire code for this example:

/site/templates/blog.json.php
<?php

$data = $pages->find('blog')->children()->published()->paginate(10);
$json = [];

$json['data']  = [];
$json['pages'] = $data->pagination()->pages();
$json['page']  = $data->pagination()->page();

foreach($data as $article) {

  $json['data'][] = array(
    'url'   => (string)$article->url(),
    'title' => (string)$article->title(),
    'text'  => (string)$article->text(),
    'date'  => (string)$article->date()
  );

}

echo json_encode($json);

Next steps

It's up to you what you build with this on the front-end. I'm sure you already got some great ideas :)

This tutorial is not limited to a blog-scenario. You can easily adapt this to make other parts of your site available as JSON. You could even build a port to XML or CSV. There are hardly any limits.