Kirby3 Janitor - a panel button to run jobs defined in php

I am creating posts for my plugins to make it easier to find them using the forum search and not just the docs search.

Kirby 3 Plugin for running jobs.

  • It is a Panel Button!
  • It has jobs build-in for cleaning the cache, sessions, create zip-backup, pre-generate thumbs, open URLs, refresh the current Panel page, download a file, copy to clipboard and more.
  • You can define your own jobs (call API hooks, play a game, hack a server, …)
  • It can be triggered in your frontend code and with CRON.
  • It can also be used as a CLI with fancy output.
  • It can also create logs of what it did.

register and define a job as callback aka closure


'' => [
    'aweSomeItCouldBe' => function (Kirby\Cms\Page $page = null, string $data = null) {
        // $page => page object where the button as pressed
        // $data => 'my custom data'
        return [
            'status' => 200,
            'label' => $page->title() . ' ' . $data,
    'query' => function (Kirby\Cms\Page $page = null, string $data = null) {
        return [
            'status' => 200,
            'label' => $data, // this is the current panel users email

add a panel button


    type: janitor
    label: Awesome
    progress: Awesome...
    job: aweSomeItCouldBe

    type: janitor
    label: Query '{{ }}'
    job: query
    data: '{{ }}'

with new v2.13.0 you can now use janitor in CLI mode to

  • janitor --tinker will start a very simple REPL session.
  • janitor --down will put your frontend in maintenance mode. Panel will still be usable.
  • janitor --up will stop the maintenance mode.

Hi Bnomei,

If I want to add a button in pane which blueprint should I edit?

I tried adding this:

  type: janitor
  label: Clean Cache
  progress: Cleaning Cache...
  job: cleanCache

to site/blueprints/default.yml but nothing changes in the panel.

What am I doing wrong?


That depends on your setup and on which page type you want to use the button

1 Like

@texnixe thanks for your reply!

I thought cleaning the cache would clean the whole website’s cache – ideally it’d be a button/section in the main panel navigation, like I saw other plugins do. It’s not really tied to a page. But I think the owner of the website wouldn’t mind if it would be a button just when editing a certain page, that’s fine.

I tried adding the YAML above it to all 3 blueprints that are actually used on this website and none of the forms changed when trying to edit the pages.

It would probably make sense to include this button on the Dashboard, i.e. site.yml, not a page blueprint.

Since the Janitor button is provided as a field plugins, it needs to be inside a fields section-

Yes, that is correct.

That’s not possible with a field/section plugin.

1 Like


First I wasn’t putting it under the fields entry, second I didn’t even realise about the site/blueprints/site.yml. While upgrading from Kirby 2 to 3 I put all blueprints into blueprints/pages and site.yml wasn’t at all respected, so I thought it was an old unused blueprint. I added it to site.yml fields and the button is there, thanks so much!

But now that I have added it, the previous dashboard where new pages could be added is gone – how could I allow both site.yml fields and editing pages too?


There are several ways to set this up, but the minimum requirement for this would be a fields section and a pages section.

title: Site

    type: pages
    # other page section props.
    type: fields
      # your fields here

Of course, you can have multiple sections (e.g. pages separated by templates), and also put them into columns and tabs.


Per-fect! Works like a charm, thanks so much!

hello everyone.

i am considering dropping the custom CLI and converting my command to the new kirby CLI. so instead of janitor yourjob you would call kirby yourjob.

i am also planning to add a very easy way to run any of the kirby CLI commands using a janitor button.

whats your take on this?


:mega: version 3 released today. I refactored Janitor plugin to work with kirbys new cli commands. You can define and trigger your own command easily but you can keep using callbacks if you prefer that (see janitor:job).

    type: janitor
    command: 'ping' # see tests/site/commands/ping.php
    label: Ping
    progress: ....
    success: Pong
    error: BAMM

    type: janitor
    command: 'janitor:open --data {{ user.panel.url }}'
    intab: true
    label: Open current user URL in new tab
    icon: open
    # the open command will forward the `data` arg to `open` and open that URL

    type: janitor
    command: 'janitor:clipboard --data {{ page.title }}'
    label: 'Copy "{{ page.title }}" to Clipboard'
    progress: Copied!
    icon: copy
    # the clipboard command will forward the `data` arg to `clipboard` and copy that

    type: janitor
    command: 'janitor:download --data {{ site.index.files.first.url }}'
    label: Download File Example
    icon: download
    # the download command will forward the `data` arg to `download` and start downloading that

    type: janitor
    command: 'janitor:backupzip'
    cooldown: 5000
    label: Generate Backup ZIP
    icon: archive

    type: janitor
    command: 'janitor:render'
    label: Render pages to create missing thumb jobs

    type: janitor
    command: 'janitor:thumbs --site'
    label: Generate thumbs from existing thumb jobs (full site)


title: Default Page
    type: janitor
    command: 'example --data test'
    label: Call `Example` Command



use Bnomei\Janitor;
use Kirby\CLI\CLI;

return [
    'description' => 'Example',
    'args' => [] + Janitor::ARGS, // page, file, user, site, data
    'command' => static function (CLI $cli): void {
        $page = page($cli->arg('page'));

        // output for the command line
        defined('STDOUT') && $cli->success(
            $model->title() . ' ' . $cli->arg('data')

        // output for janitor
        janitor()->data($cli->arg('command'), [
            'status' => 200,
            'message' => $model->title() . ' ' . $cli->arg('data'),


Got: Undefined constant “STDOUT”. Was trying to remove defined('STDOUT') but without success. Running on PHP@8.0.26

janitor v3 needs the kirby cli installed. while you can run some core cli commands with the —quiet option others might fail due to the cli being optimized to run in terminal only. i created an issue but got no response yet.

in my commands i combine all calls to the cli output with a defined('STDOUT') && which essentially prevents that issue from happening.

1 Like

@bnomei I also have the STDOUT problem with my first implementation of the new janitor.
Not to test I created a fully new and empty plainkit project and followed your readme for the example command and even there I get the STDOUT :thinking: Kirby CLI is installed btw…

As a work arround I tried working with the callbacks but as soon as I add a button to my page I always get an error when adding a new page.

Example button:

  type: janitor
  icon: cog
  progress: 'Processing...'
  command: 'janitor:job --key processRegistrations'
  label: Process registrations

So when I add that and I want to create a new page I receive this:

When I remove the button everything works as expected :thinking:

Cheers @bnomei ! In regards to the other problem I’m having with the DuplicatePage exception… (I’ll keep it on the forum because i’m not sure if it’s a bug yet… I’m having a hard time to get my head around it)

I’ve tracked it down that this piece is causing it:

'command' => function ($command = null) {
    // resolve queries
    $command = \Bnomei\Janitor::query($command, $this->model());
    // append model
    if ($this->model() instanceof \Kirby\Cms\Page) {
        $command .= ' --page ' . $this->model()->uuid()->toString() ?? $this->model()->id();
    } elseif ($this->model() instanceof \Kirby\Cms\File) {
        $command .= ' --file ' . $this->model()->uuid()->toString() ?? $this->model()->id();
    } elseif ($this->model() instanceof \Kirby\Cms\User) {
        $command .= ' --user ' . $this->model()->uuid()->toString() ?? $this->model()->id();
    } elseif ($this->model() instanceof \Kirby\Cms\Site) {
        $command .= ' --site'; // boolean argument
    $command .= ' --model '. $this->model()->uuid()->toString() ??
        ($this->model() instanceof \Kirby\Cms\Site ? 'site://' : $this->model()->id());
    return $command;

If I remove the $this->model()->uuid()->toString() references then everything works as excepted. If I keep them I get the DuplicatePage exception as mentioned in my screenshot above.

I wonder if it’s somewhat related to “a-call-to-uuid-causes-a-write-on-a-virtual-page” but I’m really flabbergasted on what’s happening here… Can’t really seem to replicate it in plainkit either.

If you have any ideas … at a loss here :sweat_smile:

the last few versions added a few new features

Delaying query resolution - if you want to send JSON etc

    label: 'Resolve Arg smart/lazy using {( instead of {{'
    type: janitor
    command: 'janitor:pipe --data {( model.text.kirbytext )} --to message'
    # pipe will show lazy/smart resolved data on button (aka the message)

colors from blueprint and api

    label: Janitor buttons can now be colorful
    type: janitor
    command: 'janitor:job --key random.colors'
    color: 'var(--color-blue-600)'
    backgroundColor: 'var(--color-blue-200)'

calling methods on a model

if you do not want to create a command file or job definition in config for everything.

    label: Call method on current model
    type: janitor
    command: 'janitor:call --method whoAmI'

    label: Call method on current model with Data
    type: janitor
    command: 'janitor:call --method repeatAfterMe --data {{ }}'

Create a Backup of pages (and subpages) easily with undertaker()-helper

site/config/config.php or in a plugin


return [
    'hooks' => [
        'page.delete:before' => function (\Kirby\Cms\Page $page, bool $force) {
            // do something before a page gets deleted
            // will create a file with filename
            // site/graveyard/
    // ... other options

Create Backup ZIP of content and accounts folder

If you do not just need to archive a few pages on change but want to archive the full content and/or user accounts then Janitor also has solution for that. You can adjust which folders to archive.

Toggle Maintenance Mode

    type: janitor
    command: 'janitor:maintenance --user {{ user.uuid }}'
    cooldown: 5000
    label: 'Maintenance: {{ site.isUnderMaintenance.ecco("DOWN","UP") }}'
    icon: '{{ site.isUnderMaintenance.ecco("cancel","circle") }}'


Read more about these features in the README