featured image

Grav - Adding cached variables to pages

What is Grav?

If you don't know what is Grav then it's time to check it out! It's a blazing fast modern open source flat-file CMS. I can't recommend it enough if you want to build your next portfolio or any kind of website. If you love to hack with PHP then it's the right choose!

Why would I add dynamically data to a page when I can do that via frontmatter?

In Grav you can easily add custom data to your page via frontmatter but what if you want to dynamically add these data to all of your blog posts in a certain folder?

In our situation we have a directory structure like this:

├── user1/
│   └── posts/
│       ├── my-post-1/
│       │   └── post.md
│       └── my-post-2/
│           └── post.md
├── user2/
│   └── posts/
│       ├── my-post-1/
│       │   └── post.md
│       └── my-post-2/
│           └── post.md
└── user3/
    └── posts/
        ├── my-post-1/
        │   └── post.md
        └── my-post-2/
            └── post.md

As you can see it's easy to distinguish between the users' posts. So how does a post's frontmatter looks like?

title: My Post 1
date: '12-04-2017 18:53'
        - blog
        - post
    twig: true
Posted by {{ user.fullname }}

It is easy to see that our page doesn't even know about the user! Well, according to the frontmatter.

What do we want to achieve?

We want to make sure that every post in the posts folder will automagically have a user variable.

For example: pages/user1/posts/my-post-1 must have a user variable which includes the details of the user1 account.

magic comes below

class InjectUserPlugin extends \Grav\Common\Plugin {

  public static function getSubscribedEvents() {
    return [
      'onPageProcessed' => ['injectUser', 0],

  public function injectUser($e) {
    // If we processed the page through the admin panel
    // then we skip this, so it won't persist the dynamic
    // values into the file
    if ($this->isAdmin()) {

    $page = $e['page'];
    $rawRoute = $page->rawRoute();
    foreach ($this->users() as $user) {
      if (strpos($rawRoute, $user->username) === 1) {
        $page->header()->user = $user;

  public function users() {
    $users = [];

    $dir = $this->grav['locator']->findResource('account://');
    $files = $dir ? array_diff(scandir($dir), ['.', '..']) : [];
    foreach ($files as $file) {
      if (\Grav\Common\Utils::endsWith($file, YAML_EXT)) {
        $user = \Grav\Common\User\User::load(trim(pathinfo($file, PATHINFO_FILENAME)));
        // We don't need these
        unset($user['hashed_password'], $user['access']);
        $users[$user->username] = $user;

    return $users;



onPageProcessed is fired after a non-cached page is parsed and processed. This means we can use this to run some before-cache changes on the Page object! You can learn more about event hooks at Grav Event Hooks.

If the route starts with any user's username then it adds that user object to the page's header. It will be put into the cached page but it won't be persisted to the raw file. It's that simple.

strpos($rawRoute, $user->username) === 1 route starts with a / so we need to check if the username is on position 1.

This is a dead simple example how you can dynamically add (cached) values to a page. This is something we make a good use of at MessedCode. To be honest, this is how our blog posts works!

I hope you enjoyed this post and you will make a good use of this!