Files sections used to be the only way to handle direct file uploads in the Kirby Panel. Since version 3.2.0 (6 years ago?!), files fields support this too and theyāre often a better choice. Unfortunately, migrating the content from one approach to the other is quite complex, so Iāve held off touching this on some older websites.
Recently Iāve finally spent some time finding an efficient solution and I want to share it with you.
About sections and fields
In Kirby, sections are used to display content in the Panel, while fields are used to store content. Most of the times you want to have both a files section and (multiple) files fields. The section lists the files for your page and the fields allow you to select specific files for specific purposes like a slideshow.
Because it was not possible to upload files to the files field directly, I often used only a files section for these specific purposes. This way my users didnāt have to navigate to the files section to upload some files and then select the same files in another place. They just uploaded the files in the section and āselectedā them right there. Now that we can upload files directly in the files field, this is no longer necessary and only comes with downsides.
The necessary changes
First, letās see what weāll actually have to change in the blueprint and the content files.
Adjusting the blueprint
We want to change the blueprint from a files section with a templateā¦
sections:
images:
type: files
template: slideshow
ā¦to a simple files field allowing the user to select any image on the page:
fields:
images:
type: files
query: page.images
Thatās easy enough. We donāt really need to automate anything here. But obviously weāre not even close to done with the migration.
Letās look at the difference in the content files next:
Adjusting the content files
From individual image content files with a common template like these:
Alt: An image of a cute cat
----
Uuid: NaacIvCVo48DeHrT
----
Template: slideshow
Alt: Another image of a cute cat
----
Uuid: mdNBF6k1jhqJr6DQ
----
Template: slideshow
Alt: An image of an extra fluffy cat
----
Uuid: qBGsw42NnLGGRBo6
----
Template: slideshow
To a simple list of UUIDs in the pageās content file:
Slideshow:
- file://NaacIvCVo48DeHrT
- file://mdNBF6k1jhqJr6DQ
- file://qBGsw42NnLGGRBo6
That means we need to find all images with a specific template (e.g. āslideshowā), get their UUIDs and store them in a field on their parent page.
Automating the migration
This change could take a long time, when done manually. Thankfully Kirby has a CLI and itās quite easy to create custom commands for it.
My Kirby CLI command
This command does the following things:
- Looping through all pages ā We use ā
site()->index(true)
to get all pages including drafts - Finding relevant images ā For each page, we filter images by the specified template and maintain their sort order with
ā ->sorted()
. - Collecting UUIDs ā We extract the UUID from each matching image and build an array.
- Updating the page ā Using Kirbyās
ā impersonate()
method to run with admin privileges, we update the page with the new field containing the image UUIDs.
<?php
declare(strict_types=1);
use Kirby\CLI\CLI;
return [
'description' => 'Migrate from files sections to files fields',
'args' => [
'template' => [
'prefix' => 't',
'longPrefix' => 'template',
'description' => 'File template to migrate',
'defaultValue' => 'slideshow'
],
'dry' => [
'longPrefix' => 'dry',
'description' => 'Dry run',
'defaultValue' => false,
'noValue' => true,
],
],
'command' => static function (CLI $cli): void {
$isDryRun = $cli->arg('dry');
$template = $cli->arg('template');
$pages = site()->index(true);
foreach ($pages as $page) {
try {
$cli->out("š /" . $page->id());
$images = $page->images()->template($template)->sorted();
if ($images->count() === 0) {
$cli->out("āļø skipped\n");
continue;
}
$imagesArray = [];
foreach ($images as $image) {
$imagesArray[] = $image->uuid()->toString();
$cli->out('š ' . $image->name());
}
if (!$isDryRun) {
kirby()->impersonate('kirby', function () use ($page, $imagesArray, $template) {
$page->update([
$template => $imagesArray
]);
});
}
$cli->out("āļø " . count($imagesArray) . " image(s)\n");
} catch (\Exception $e) {
$cli->error("Error on page /" . $page->id());
$cli->error($e->getMessage());
$cli->error($e->getTraceAsString());
break;
}
}
$cli->success('All pages have been updated');
}
];
Installing the command
Have a look at the documentation for global CLI commands.
TL;DR: Put the code in ~/.kirby/commands/files-section-to-field.php
Running the command
Test it first with a dry run (no changes will be made):
kirby files-section-to-field --dry
Then run the actual migration:
kirby files-section-to-field
If your image template happens to be something other than āslideshowā, you can specify it with the template argument:
kirby files-section-to-field --template cat
Always back up your content before running commands you find on random peopleās websites! Thankfully thatās super easy with file-based systems like Kirby: just drag the content folder to your desktop.
Final thoughts
Iāve been putting off this migration for years because it seemed like such a tedious task. Creating a CLI command turned out to be quite straightforward and saved me hours of manual work across multiple projects.
Am I the only one that used separate files sections just for file uploads? I canāt be the only one, right? If this post helped you, please let me know! You can find me on Mastodon.