ūüöÄ A new era: Kirby 4 Get to know
Skip to content

Kirby 3.9


Snippets with slots

Templating on another level

<?php snippet('article', slots: true) ?>
  <?php slot('header') ?>
    <h1>This is the title</h1>
  <?php endslot() ?>

  <?php slot('body') ?>
    <p>This is some body text</p>
  <?php endslot() ?>
<?php endsnippet() ?>
  <?php if ($header = $slots->header()): ?>
    <?= $header ?>
  <?php endif ?>

  <?= $slots->body() ?>
Snippets with slots turn your PHP snippets into rich components. Pass full code blocks into a snippet and output them right in the middle of the snippet where you need them.
The contents of each slot are captured and passed to the snippet in the $slots variable. Snippets can have multiple slots and you can even combine slots with data variables.
Use snippets with slots for simple components like buttons or even for full page layouts. You can nest snippets within slots of other snippets for full flexibility.

Structure field improvements

New row & delete all buttons

PHP 8.2 support

PHP moves forward, we do too

Satellite Releases

Ecosystem improvements

Kirby CLI 1.1.0

The Kirby Command Line Interface is getting a big update. Kirby plugins can now define their own commands for the CLI, you can register your installation without going to the Panel and more …
Kirby::plugin('your/plugin', [
  'commands' => [
    'your-plugin:test' => [
      'description' => 'Nice command',
      'args' => [],
      'command' => function ($cli) {
        $cli->success('My first plugin command');
$ kirby your-plugin:test
# My first plugin command

Kirby KQL 2.0.0

Kirby's Query Language API combines the flexibility of Kirby's data structures, the power of GraphQL and the simplicity of REST.
KQL 2.0.0 comes with all the new query syntax improvements from Kirby 3.8.3 and a fully refactored code base.
The new KQL Playground gets you started with a set of example queries. Connected to our Starterkit, you can play around with your own queries.

Staticache 1.0.0

Staticache is our alternative page cache plugin for Kirby that stores responses as fully static files. This helps you to increase your site performance even more.

With version 1.0.0 it is finally ready for prime time for a lot of common site setups. It now comes with support for multi-language sites and content representations as well as with a new mode that caches custom HTTP response headers as well.

return [
  'cache' => [
    'pages' => [
      'active'  => true,
      'type'    => 'static',
      'root'    => '/path/to/your/cache/root',
      'comment' => '<!-- your custom cache comment -->',
      'headers' => true

Eleventykit ūüéą

The Eleventykit is a very simple (unstyled) example for a site, built with Kirby and 11ty.

Kirby’s query language (KQL) is used to fetch articles from our KQL playground: https://kql.getkirby.com

Use your own Kirby installation with the KQL plugin to provide a powerful headless CMS for your static 11ty site.

11ty: /_data/posts.js
const { $fetch } = require("ofetch");

module.exports = async function () {

    const api = "https://kql.getkirby.com/api/query";

    const response = await $fetch(api, {
        method: "post",
        body: {
            query: "page('notes').children.sortBy('date', 'desc')",
            select: {
                title: true,
                text: "page.text.toBlocks.toHtml",
                slug: true,
                date: "page.date.toDate('d.m.Y')"

    return response.result;




  • Snippets with slots
  • Structure field improvements
    • Delete all button idea 458
    • New add button below last row
    • Better pagination
  • PHP 8.2 support #4890


  • Image\Image¬†uses its model's¬†alt¬†content field as fallback for rendering the¬†alt¬†attribute to better provide accessible defaults (we already did it for direct image objects, but now it also works for thumbs etc.) #4915
  • Pages section: the status flag button‚Äôs tooltip now includes the non-customised label of the current status for better accessibility #4928
  • The license registration dialog now displays an info field for which domain the license will be registered, including a warning when registering a local domain #4930
  • Dimensions::forSvg() supports percentages and better viewport fallbacks #4921
  • Xml::attr()¬†and¬†Html::attr()¬†accept non-associative values (e.g.¬†Xml::attr(['attrA'])), which get rendered like passing¬†true¬†(e.g.¬†attrA="attrA"/attrA) #4935
  • The¬†Dir::copy()¬†method now supports¬†false¬†as the argument for¬†$ignore. In this mode, all files are copied, including those listed in the global¬†Dir::$ignore¬†list. #4872
  • F::move() now ensures that the parent directory exists, which is consistent with F::copy() #4943
  • File::create(), $parent->createFile() and $file->replace() support a new mode that moves the source file instead of copying it #4943
  • Sensitive information like passwords is redacted in logs when using PHP 8.2+ #4945

Bug fixes

  • Dynamic options for the select field (from API or query) are no longer displayed with double-escaping in the Panel #4974
  • Options from API in options fields (e.g. select, checkboxes) now support simple key/value data like¬†{"key": "value"} again #4987
  • Using options from API in options fields (e.g. select, checkboxes) also works without the¬†query¬†setting again¬†#4985
  • Writer: links now fully wrap inline styling instead of generating multiple link parts #4866
  • Kirby queries: optional chaining on non-null values now works properly #4901
  • Escaping quotes inside query arguments with¬†\"¬†and¬†\'¬†now behaves like it does in PHP (only the same type of quote used for the string can be escaped) #4976
  • Uploaded files are deleted from the temporary directory after they have been successfully stored in the content directory. #2476
  • Fixed focus for checkboxes and radio input with no options #4917
  • Fixed undefined error when paginating the structure field #5000
  • The¬†page.changeSlug¬†hook now receives the correct¬†$languageCode #4983
  • Files field: for¬†store: id¬†only the name is stored again when the current model is the file's parent #4870
  • Fixed prepending redundant¬†mailto:¬†in¬†k-link #4882
  • Toolkit\Xml::attr() is case-sensitive now #4911
  • Now treating paths with trailing dot as non-existing content representation #4920
  • Fixed console errors for failed¬†lock ¬†request when deleting page with unsaved changes #4919
  • Fixed error message for missing field type #4929
  • ->words()¬†field method works as expected on¬†null¬†values #4905
  • Fixed false-positive blocked requests by ModSecurity (OWASP rules) #4933
  • Panel: load development icons file when in dev mode #4900
  • I18n: English translation had February set to¬†Feburary #4903


  • Improvements for¬†Toolkit\Locale¬†code quality #4926
  • Improvements for¬†Toolkit\Escape¬†code quality #4925
  • Improvements for¬†Toolkit\Obj¬†code quality #4923
  • Improvements for¬†Toolkit\A¬†code quality #4942
  • Improvements for Toolkit\I18n¬†code quality #4939
  • Improvements for¬†Toolkit\Silo¬†code quality #4922
  • Improvements for Toolkit\Controller¬†code quality #4937
  • Improvements for¬†Toolkit\Iterator¬†code quality #4938
  • Improvements for Toolkit\Html and Toolkit\Xml code quality #4959
  • Improvements for Toolkit\Str code quality #4961
  • Improvements for Text code quality #4956
  • Improvements for Blueprint and Option code quality #4974
  • Upgraded to Vite 4 and some other JS dependencies #4912
  • Updated Composer dependencies #4946
  • PHP 8.2 prep: Fix dynamic properties in search component #4888
  • PHP 8.2 prep: Fix¬†mb_convert_encoding()¬†deprecation¬†in Parsedown #4887
  • The Kirby\Cms\Template class moved to Kirby\Template\Template. An alias has been added. #4910
  • Our unit tests now always trigger our own deprecation warnings, which allows us to find uses of deprecated code in our own code. #4948
  • Use of the null coalescing assignment operator ??= where possible #4957
  • Use of instanceof, the .= operator, array_key_exists() and property defaults where possible #4958
  • Nested if statements have been combined/removed #4958
  • More of our PHPUnit tests use a temporary directory now to prevent tests from creating a stray site directory in the top-level of the project #4973
  • Fixed type hints and docblocks throughout the codebase #4971
  • Fixed broken link in the contribution guide #4960
  • Our PHPUnit tests consistently use¬†assertSame¬†instead of¬†assertEquals¬†where possible and use more specific assertions (e.g.¬†assertTrue,¬†assertNull,¬†assertFileExists,¬†assertArrayHasKey) #4975
  • Fixed ghost output during PHPUnit test runs #4964


  • Manually passing $slot or $slots as data variables to snippets is deprecated. In a future Kirby version, those variables will be overridden with the Slot and Slots objects. #4963
  • The Toolkit\Query class has been deprecated and will be removed in a future version. Use Query\Query instead. #4944
  • Passing an empty string as value to¬†Xml::attr()¬†/ Html::attr() has been deprecated and will throw a warning. In a future version, passing an empty string won't omit the attribute anymore but render it with an empty value. To omit the attribute, please pass¬†null. #4934
  • The .k-offscreen CSS class has been deprecated. Use .sr-only instead. #4944

Breaking changes

We try to avoid breaking changes as much as we can. But we also put a lot of effort in keeping our technical debt in Kirby as low as possible. Sometimes such breaking changes are necessary to move forward with a clean code base.

You might wonder why there are breaking changes in a minor release according to Semantic Versioning.

We stick to the following versioning scheme:


This release is Kirby (major release 9 of Kirby 3)

Traditionally, we combine patch and minor releases though and only need the fourth versioning level for regression fixes.

Removed deprecated code

Migration from Kirby 3.8.x

There are not many steps needed to migrate from Kirby 3.8 to Kirby 3.9. To ease the transition, we have compiled everything you need to know in migration guides:

Migration guide for site developers ‚Üí
Migration guide for plugin developers ‚Üí

If you are updating from Kirby 3.7 or older, please first perform each major update step by step. Please refer to the changelogs of Kirby 3.5, Kirby 3.6, Kirby 3.7 and Kirby 3.8 for details.


Get started