Plugin Assets Timestamp

According to the documentation Kirby should take care of adding a timestamp to the plugin assets.

echo css("media/plugins/my/plugin/style.css");

The css is loaded but no timestamp added to the filename. Do I need to configure something?

I wonder if that only refers to Panel plugins, not frontend plugins. Or are you actually talking about Panel plugins?

I was thinking about frontend plugins.
But it is a factor for the panel aswell, i think. The plugin panel assets (index.css/index.js) get concatenated into one file for all plugins (media/plugins/index.css and media/plugins/index.js) without a timestamp.

For the Panel, it doesn’t add a timestamp but generates a hash, see kirby/src/Cms/PanelPlugin.php:

     * Returns the unique hash for the cache file
     * The hash is generated from all plugin filenames
     * and the max modification date to make sure changes
     * will always be cached properly
     * @return string
    public function hash(): string
        if ($this->hash !== null) {
            return $this->hash;

        return $this->hash = $this->id() . '-' . $this->modified();

hm… my media folder looks like this:

There is a panel folder with that hash containing the Kirby panel core files. But the plugin assets end up in the plugins folder without any hash or timestamp.

I can see what PanelPlugins should do, but something is not working because the plugins folder should endup inside the panels folder with an extra hash.
Maybe related to this bug is that index.css and index.js have a . (dot) in front of their name.

Hm, in my folder it looks like this:


so that looks a bit different from what you have there and indeed doesn’t look like it works as it should.

After updating to Kirby 3.1.3 the panel caching works! (for plugin assets).

Refactored way of loading css and js plugins for the panel with automatic garbage collection of removed plugins.

Not for custom panel css and js though, and the issue with plugin assets in the frontent is still there.

If I try to add a cachebusting string by overwriting the css() / js() helpers, I fail at getting the correct filemtime() because the url the helpers get is a route and not an actual file…any ideas?

Hm, I tested the example from the docs, and it works at least for frontend CSS, plugin assets and @auto assets:

This in the header:

<?= css([
]) ?>

returns these link tags:

<link href="http://kirby.test/assets/css/index.css?ea0b15243b1ebacb018d4e94fb7989e9" rel="stylesheet">
<link href="http://kirby.test/assets/css/templates/home.css?f4fba9d554080ac7fea50938a30bd949" rel="stylesheet">  
<link href="http://kirby.test/media/plugins/starterkit/gallery/css/plugin.css?219bc4c3e7e8e544b74004acbe59a700" rel="stylesheet">

I downloaded a fresh starterkit and added a site/plugins/gallery/assets/style.css.
Then I copied the css component from the link you posted.

When I try this in a template:

<?= css("media/plugins/starterkit/gallery/style.css") ?>

An Exception is thrown:

Whoops\Exception\ErrorException: md5_file(media/plugins/starterkit/gallery/style.css): failed to open stream: No such file or directory

When I open the path in my browser http://localhost/3.1.3/media/plugins/starterkit/gallery/style.css it does show the css file. I noticed that the style.css is not copied to the media folder but seems to be routed to the plugins folder (or loaded from there).

So if I add md5_file(url($url)) it does actually work.
But now I am requesting the file via a http request to md5 it and generate the hash.

Is this a good solution? Would it not be better to access the file via the filesystem?

I’ll have to check tonight what I did differently so that I got it to work.

I can’t make it work with a pure filesystem solution. From the url I cannot guess what folder the plugin is in.

Is this feature from the docs: /media/plugins/superwoman/superplugin/fields/myfield.1520265293.js supposed to work or is it missing?

In my example setup, I’ve put the file into the following folder


The namespace is starterkit/gallery.

CSS helper:

<?= css(['media/plugins/starterkit/gallery/css/plugin.css']) ?>

So media/plugins + namespace - assets folder name + css folder name + asset name

Resulting link with custom CSS component

<link href="http://kirby.test/media/plugins/starterkit/gallery/css/plugin.css?219bc4c3e7e8e544b74004acbe59a700" rel="stylesheet">

But without the custom component, no cache busting added on frontend.

The example from the docs is also not quite correct, it seems, but it refers to a panel plugin.

When I do what you describe, I end up with the Exception thrown from md5_file().
This makes sense because the url I get in the css component is relative but to a file that does not exist.

Is in your setup the plugin.css copied to the media folder?

I am not sure what the difference would be then between a “Panel Plugin” and a “Frontend Plugin”. As long as I call media/plugins/starterkit/gallery/css/plugin.css it goes the same routing way.

You have already posted issues on GitHub, so I think it’s better to let the devs figure that out.

But the documentation in that regard is definitely not in line with the current implementation.

Thanks for your time @texnixe. Since it is working on your system I kept asking.
I don’t think my opened issues cover this problem.

Here is my working example so that we are on the same page:

That works as long as I don’t touch the media folder. As soon as I delete it and let my machine take care of it, I run into the Exception.
plugin.css is not copied to the media folder anymore :frowning:

This seems to be the same issue: Symlinks for plugin assets are not working reliable

depending on server.

You are testing this locally, aren’t you? What is your environment?

1 Like

You are right, symlinks seem to be the problem.

I am running Laragon with Apache httpd-2.4.35-win64-VC15 on Windows 10.

There must be something wrong with symlink() on windows.
Since PluginAssets returns the “original” file’s content if the symlink fails it is no problem as long as you don’t try to actually do something with the file in the media folder. In my case md5_file()

Then maybe wrap an if statement around the md5_file() call.