Managing content

We’ve all been there: the endless struggle of getting content from clients. At best, it’s a constant back-and-forth of emails. At worst, you’re handed scanned documents with low-resolution images, one email at a time 😱

Once you’re working in the content management system, things usually go a lot smoother. There’s a single source of truth, you can easily validate content and everything is neatly grouped into pages. It’s great. But it comes too late!

The content-first approach

I don’t think I need to convince anyone that the ideal project should start with content, followed by content-driven design. Only this way, design decisions are based on actual content rather than placeholder text, and technical solutions are working with real content.

But after spending what felt like half of last year managing, structuring, fixing, and (mostly) waiting for content, I realized something needed to change.

Content management with a content management system

I tried all kinds of tools and services to help me with this process, but none of them felt right. Not flexible enough, too complicated for clients, what are they doing with my data, …

I’m not a stranger to using my favorite CMS for all kinds of weird stuff. But this time it’s actually quite the no-brainer. What’s a good system for managing content? A content management system!

With Kirby 5 around the corner, I saw some promising features that could help me with this project. It was also a good excuse to test the beta. After using it for the first few projects I want to share it with you. Maybe it helps you get started with something similar?

Kirby panel in dark mode showing a page called My Project with an editable info field, an email field, a folders pages section and a global files section
A project in the admin view
Kirby panel in light mode showing a page called My Project with an info field, a folders pages section and a global files section
A project in the client view

Clients and partners can collaborate on the content in a simple Kirby installation with folders (in infinite levels) and files. They can use the folders for pages or literally as folders for grouping content. This is very close to Kirby’s file-based nature where pages are folders, too.

This is how a page looks like in this sample project:

Kirby panel in dark mode showing a page called Home with an empty, editable info field, a pages section called subfolders, a files section and a blocks field with the label Content.
An example folder/page in the admin view
Kirby panel in light mode showing a page called Home with a pages section called subfolders, a files section and a blocks field with the label Content.
An example folder/page in the client view

On every level, admins can optionally put text in an info field, letting others know about what’s needed, the current status, milestones, links or just saying “Hello!”. If the field is empty, it doesn’t show up in the client view.

For each file, I added a textarea called “File information”. So far, that seems to be enough on a file level:

Kirby panel in dark mode showing a file with a textarea with the label File information.
An example file in the admin view
Kirby panel in light mode showing a file with a textarea with the label File information.
An example file in the client view

How it works

Editable info fields

I added fields on all projects and pages that only admins can edit. For clients they look like info fields.

My first idea was disabling the fields but I quickly realized this has two major disadvantages: it looks disabled (duh) and links are not clickable. I want to format the text and I want to be able to link to the design file for example. It’s also cool how the email button is the same color as the info field, suggesting it’s the same person (me, mostly).

That’s why I went for a programmatic blueprint section:

'blueprints' => [
  'sections/status' => function () {

    // Initialize base section structure
    $section = [
      'type' => 'fields',
      'fields' => []
    ];

    // Admin users get a textarea and an email field
    if (kirby()->user()->role()->name() === 'admin') {
      $section['fields'] = [
        'info' => [
          'type' => 'textarea',
        ],
        'email' => [
          'label' => '{{ t("email") }}',
          'type' => 'email'
        ],
      ];
    }

    // Non-admin users only see an info field
    else {
      $section['fields']['info'] = [
        'type' => 'info',
        'text' => '{< model.info.kt >}'
      ];
    }

    return $section;
  },
]

This is surprisingly clean and so far it worked really well for communication. In the future I might want to allow different themes but for now I’m very happy with the default info.

Panel view buttons (Kirby 5)

One of the coolest new features in Kirby 5 came just in time (technically not, but the beta is quite stable): Panel view buttons.

This is how the buttons configuration looks in the blueprint for projects:

# site/blueprints/project.yml
buttons:
  email:
    icon: email
    text: '{{ t("email") }}'
    link: 'mailto:{{ page.email }}'
    theme: info

It removes the default preview, settings and status buttons and replaces them with an email button.

In the folder blueprint I set a custom status and only show that button:

# site/blueprints/folder.yml
status:
  draft:
    label: In progress
  listed:
    label: Content ready

buttons:
  - status

Making sure clients can only see their own projects

Since all of this happens in a single installation, I need some way of restricting users to specific projects. Luckily that’s very easy in Kirby.

In the project model I overwrite the isReadable() function:

use Kirby\Cms\Page;

class ProjectPage extends Page
{

  public function isReadable(): bool
  {

    if (!$this->kirby()->user()->isAdmin()) {
      $allowedProjects = kirby()->user()->projects()->toPages();
      return $allowedProjects->has($this->id());
    }

    return true;
  }
}

In the client role I added a projects pages field:

title: Client

# Redirect to first project after login
home: "{{ user.projects.toPages.first.panel.url }}"

# Hide all other views from clients
permissions:
  access:
    users: false
    system: false
    languages: false
  user:
    update: false

# Projects field
fields:
  projects:
    type: pages
    subpages: false

With this in place, clients can only see the projects I want them to see in the dashboard:

Kirby panel in dark mode showing the dashboard with a pages section called Projects with 6 projects.
Dashboard in the admin view
Kirby panel in light mode showing the dashboard with a pages section called Projects with 1 project.
Dashboard in the client view

Frontend redirects

Frontend requests are redirected to the panel with a simple route. This way I can also send short links to subpages. For example /my-project/about/company will get redirected to /panel/pages/my-project+about+company:

'routes' => [
  [
    'pattern' => '(:all)',
    'action' => function ( $path) {
      $page = page($path);
      if ($page) go($page->panel()->url());
      go(site()->panel()->url());
    }
  ]
]

If the page is not (or no longer) available, I redirect them to the panel dashboard instead.

Email notifications

I collect changes and regularly send emails to myself. I’ll extend this part of the article once I find some more time. 🚧

Roughly, this is how it works:

  1. On any file/page hook, I log the change to a field with the page, the user, time and action
  2. Every hour a cronjob visits a route (with authentication)
  3. The route collects the changes, sends an email and empties the field again

Verdict

This setup has been a game-changer for my project workflow. Clients get a clean, focused interface while I maintain full control. Clients are already getting used to the Kirby interface before I even write a single line of code. Once the content is ready, I can easily export it to the actual website project – it’s already in the right format.

Is it perfect? Definitely not. It already went through a lot of changes and I’m sure there will be many more. But I feel very confident with this setup.

What do you think? Let me know on Mastodon.