With Kirby, you can merge and aggregate all sorts of content from different sources. This does not only work for pages that you can create from APIs, spreadsheets or databases, but also for files. In this recipe, we will look into two options how we can implement this (and briefly mention another alternative without actually implementing it).
Note that while the two options discussed in this recipe focus on images, it would work just as well for other file types.
- A running Kirby installation: Plainkit or Starterkit
- A code editor
- Basic understand of Object Oriented Programming is helpful for a better understanding of the second example
Ready? Let’s get going…
Since files in Kirby always belong to either a page object or the site object, let’s first create a new
photos page in the
/content folder and add a text file called
The other files we need for the two example implementations all go into a plugin, so that we have everything we need in one place.
So the second step is to create a new plugin folder at
/site/plugins/virtual-files with the obligatory
index.php inside it.
For our first example, we need a page model for the virtual files logic, a template to render the results of our endeavors on the frontend, a blueprint for the page in the Panel, and we also add a file blueprint for later use.
index.php file we require the file that will contain the page model, and register blueprints, page model and template:
Next, create those four files in the file system according to the following file structure:
Let’s continue with creating the page model that overwrites the original
files method. The
files method by default returns the files that physically exist in the current page folder. Instead, we will use an array with file props. The basic props we need here are the filename and the URL, but we also add a template property, so that we can later show file information in the Panel’s file view. Then we pass this array to the
$files array in this example I picked some random URLs from Unsplash, but you can of course replace these with any file URLs of your liking.
We also want a template to render the files on the frontend. For the purposes of this recipe, we keep this very simple:
Let’s check the result on the frontend. Open the page URL in a browser, for example
http://localhost/photos (or whatever your base URL is).
Great, that worked! Wasn’t this easy?
Before we can visit the page in the Panel, let’s create a basic page blueprint for the page…
…and modify the
pages section in
site.yml so that we can easily access the
If you now visit the page in the Panel, the file cards will be there, but oh, no file previews!
Why’s that? It worked on the frontend, after all? The reason why the images don’t work in the Panel (yet) is that the Panel uses thumbs for the previews, but Kirby cannot create thumbs from those virtual files.
For the moment, we therefore have to tell Kirby that we want to use the original file instead of a thumb.
file::version component allows us to define what Kirby should deliver when a thumb is requested. This component needs to either return a
File object or a
Kirby\Cms\FileVersion object. Here, we therefore return the original file instead of a thumb if the file template name is
virtual-file, otherwise we let the the native component do its job.
Put the following code into a separate file (see filepath) and then register it in
The file structure in the plugins folder should now look like this:
If we head back into the Panel, the file previews will finally be rendered.
In a real world project, the files would probably come from a database or an API. Let’s see how we can do that using the Unsplash API as an example.
If you don’t want to create an account with Unsplash, you can skip this section, but we will continue with the API for the rest of the recipe. To be able to use the API, create an account, add an application and grab your API key as described in the Unsplash API documentation.
All we need to do to get some images from Unsplash is change the page model a little bit.
To make the example work, first add your Unsplash API key to your config file:
Then replace the hardcoded
$files array from the previous example with the code below:
Remote::get() with the Unsplash API URL as parameter to fetch some images, and if we get a result, we use the data to populate the
$files array. In addition to the data from the example above, we also add some virtual metadata, like alternative text, photographer, etc.
The endpoint we are using here returns a single page of all photos with 10 items by default, sorted by latest photos. You can use the query parameters
per_page to change the number of items returned per page, or
order_by to change the sorting order. Check out the Unsplash API documentation for these parameters or for other endpoints you can use.
To round off the exercise, let’s add a blueprint for the files, so that the metadata entries will be visible in the Panel when we open the file view.
Note that we also set all files options like
update to false, because these settings don’t make sense in our current use case.
And with this, we have arrived at the end of our first implementation option. Time for a coffee (tea) break, maybe?
While the first option above works well for a simple setup, we might end up with complex logic in the
file::version component if we wanted to include files from different sources, use different file sizes for images, or do other crazy stuff.
We will be much more flexible if we use a custom file class that extends the
Kirby\Cms\File class, instead. Such a setup will not only give us the opportunity to update file metadata in a database directly from the Panel, add additional properties to our files, and a lot more if we wanted to do that, but also use different file models for different page types.
Let’s start refactoring our code…
Since we don’t need the
file::version component for this second example anymore, remove or comment the correspoding code in
index.php, so that it looks like the very first version above.
Next, we want to create a custom File class that we call
VirtualFile. It extends the Kirby
Kirby\Cms\File class and therefore inherits all its properties and methods.
Since we can now overwrite all
File class methods or add new properties and methods as needed, you probably get an idea of the powerful potential of this approach.
index.php, we now have to require this file:
The next file that needs to be changed is the page model from the last example with the call to the Unsplash API. Most of the code is the same, with two basic changes.
Note that we also extended the the data array we pass to the new
VirtualFile object with the
In this example, we use the call to the Unsplash API again, but you can replace this with the simple data array from the first example or with some data from other sources.
If you followed the examples with with the Unsplashed API and cared to take a look at the data that is returned for each image, you may have noticed that the API calls returns multiple URLs for different images sizes (raw, full, regular, small, and thumb). But by passing a query parameter to the
raw URL, we are absolutely flexible with regard to file dimensions, quality or even mime type.
Unsplash uses Imgix under the hood, so this works exactly the same as with any other CDN, and thus the implementation we describe here can be used with other CDNs as well.
Let’s leverage this possibility to create thumbs for our images by modifiying the
thumb() method of our custom
In our example above, the
thumb() method simply returned the file object. Now we can modify the method to instead return an instance of the same class, but with a modified URL if supported options are passed in the options array.
In this method we check if the options array is empty. If not, we build a query string and return a modified instance of the
VirtualFile object with a modified URL, i.e. we add the query string. You can read more about the supported parameters for dynamically resized images in the Unsplash documentation.
Note that we also created fallbacks for
crop for Kirby’s standard option parameters, which allows us to also use the
crop shortcuts in our code instead of the
Instead of handling the thumb "generation" in the
VirtualFile class, this could also be done in a
file::version custom component, which we explained in our Kirby loves CDN recipe.
In our template, we can now call the
thumb() method with any supported parameters, including different mime types like
webp via the
As already mentioned, if you don’t want to use these more complex thumb options, the simple
resize() methods will do as well:
With this implementation in place, thumbs will now also work in the Panel, of course.
Even if we know the file dimensions, we cannot simply pass the data to the
VirtualFile constructor, because
dimensions are not a property of the
File class we are extending here but a property of the more specific
Kirby\Image\Image class which itself extends the
However, as we said above, we are free to add new properties and methods to our class, so if our data source provides this information, we can pass the properties to the class constructor.
Let’s add a
dimensions property and overwrite the original class constructor with a custom constructor. We then set the value of the new property to a
Kirby\Image\Dimensions object, and return the parent constructor with the given props.
And since we are at it, we might as well add some methods to retrieve the
orientation from the new
dimensions property. The ratio and orientation calculation is done by the
Dimensions class from the width and height properties.
Now all that’s left to do is pass the width and height we get from the API to the constructor in our
files method in the page model:
To round this off, I want to briefly mention another way to store images in the cloud instead of in the filesystem, that has been mentioned multiple times by Bastian.
The idea is to upload files to the file system like normal, and then use a
file.create:after hook to upload the files to a remote blob storage like Azure or Amazon S3. Once the file is successfully uploaded, replace the actual file on disk with a 1x1px placeholder. In the content text file you can store additional metadata like the original dimensions, size and a reference ID to the uploaded file.
file::url component can then take care of creating the correct URL to the file based on the reference ID stored in the metadata.
This way, Kirby will treat the files like normal files on disk, while they are actually tiny and don’t take up much space, while the large original files are stored in cheap blob storage.
In this recipe we explored two different ways to handle completely virtual files in Kirby. The third option still leaves placeholder files on disk and works best with image files. Depending on your use case and how flexible you want to be, you can now pick the one that is the best fit for the task.