Content representations

One of Kirby's coolest features are content representations. As the name implies, they let you output your content in different formats.

What are content representations?

Content representations let you serve a pageā€™s content in different formats by adding a file extension to your URL. For example, a page at example.com/about could also be available as about.json and about.xml, each with a different representation of the content.

You can find more information and a few examples in Kirbyā€™s docs about content representations.

Why do I write about this feature if itā€™s already documented so well? Because I want to inspire you to use it more often! In this post I want to focus on simple, practical examples Iā€™ve used in real projects:

Practical examples

Calendar events (.ics)

Adding a download option to your events makes it easy for visitors to add your events to their calendar apps. This works with Apple Calendar, Google Calendar, Outlook and most other calendar applications.

The example below creates a simple event with a start date, end date, title and location.

Files:

  • Template: site/templates/event.php
  • Representation: site/templates/event.ics.php
  • Access: example.com/events/event.ics
<?php
use Kirby\Http\Header;

Header::download(['mime' => 'text/calendar', 'name' => $page->uid() . '.ics']);

// Create DateTime objects for start and end dates
$start = (new DateTime($page->start_date()))->setTimezone(new DateTimeZone('UTC'));
$end = (new DateTime($page->end_date()))->setTimezone(new DateTimeZone('UTC'));

// Format dates in iCal format (YYYYMMDDTHHmmssZ)
$startDate = $start->format('Ymd\THis\Z');
$endDate = $end->format('Ymd\THis\Z');
?>
BEGIN:VCALENDAR
VERSION:2.0
PRODID:<?= $site->title() ?>
METHOD:PUBLISH
BEGIN:VEVENT
DTSTART:<?= $startDate ?>
DTEND:<?= $endDate ?>
SUMMARY:<?= $page->title() ?>
LOCATION:<?= $site->title() ?> / <?= $page->location() ?>
END:VEVENT
END:VCALENDAR

Hereā€™s an example event for you to download and inspect.

Image archives (.zip)

Sometimes you want to provide contents of a page as a downloadable package ā€“ for example images for press releases.

This example creates a ZIP file containing all original images of a page.

Files:

  • Template: site/templates/article.php
  • Representation: site/templates/article.zip.php
  • Access: example.com/news/my-press-release.zip
<?php
use Kirby\Http\Header;

Header::download(['mime' => 'application/zip', 'name' => 'images.zip']);

$zipFile = $page->mediaRoot() . '/' . 'images.zip';
$zip = new ZipArchive;

if ($zip->open($zipFile, ZipArchive::CREATE) !== true) {
    die("Cannot open zip file");
}

foreach ($page->images() as $image) {
    $zip->addFile($image->realpath(), $image->filename());
}

$zip->close(); 
echo file_get_contents($zipFile);

āš ļø For larger sites, consider pre-generating the ZIP file. In my case, I checked for logged-in users to prevent abuse.

RSS feeds (.xml)

RSS feeds allow readers to follow your blog from the comfort of their preferred RSS reader.

This example creates a basic feed with all the essential elements like title, description and publication date. It also includes a link to the full article. You could instead decide to return the full article content in the feed ā€“ itā€™s up to you.

Files:

  • Template: site/templates/articles.php
  • Representation: site/templates/articles.xml.php
  • Access: example.com/articles.xml
<?php
header('Content-type: application/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title><?= $site->title()->xml() ?></title>
    <link><?= $site->url() ?></link>
    <description><?= $site->description()->xml() ?></description>
    <atom:link href="<?= $site->url() ?>/articles.xml" rel="self" type="application/rss+xml" />
    <language>en</language>
    <lastBuildDate><?= date('r') ?></lastBuildDate>
    <?php foreach ($page->children()->listed()->flip() as $article): ?>
      <item>
        <title><?= $article->title()->xml() ?></title>
        <link><?= $article->url() ?></link>
        <guid isPermaLink="true"><?= $article->url() ?></guid>
        <pubDate><?= $article->date()->toDate('r') ?></pubDate>
        <description>
          <![CDATA[
          <?= $article->description()->kt() ?>
          <p><a href="<?= $article->url() ?>">Read more</a></p>
        ]]>
        </description>
      </item>
    <?php endforeach ?>
  </channel>
</rss>

Fun fact: Iā€™m using this exact code on my blog here. Have a look at medienbaecker.com/articles.xml.

Plain text (.txt)

A plain text representation can be useful for various purposes. Itā€™s also simply fun to think about how you can format text with plain-text characters like itā€™s the 90s again! šŸ’¾

In the example Iā€™ve used str_repeat to visually underline the title.

Files:

  • Template: site/templates/product.php
  • Representation: site/templates/product.txt.php
  • Access: example.com/product.txt
<?php
use Kirby\Toolkit\Str;

header('Content-Type: text/plain');

echo $page->title() . "\n";
echo str_repeat('=', Str::length($page->title())) . "\n\n";
echo $page->text();

This will return something like this:

My Product
==========

The product text might be **formatted with markdown**, so the formatting characters are actually quite *readable*.

Beyond these examples

Content representations are flexible and can serve many purposes.

For example, I once used them for an info screen in a buildingā€™s entrance area. While the website showed only names and email addresses for each person, the screen included phone numbers and room numbers. Iā€™ve even used them in combination with virtual pages recently šŸ¤Æ

You just have to create a template with the appropriate file extension and content type header. The possibilities are endless!

I hope this post has inspired you to use content representations in your next Kirby project. If you have any questions or suggestions, feel free to reach out to me on Mastodon.