The gist of it

Kirby 4 will launch in 2023
We are very excited to announce the first test version for Kirby 4 with many great user-facing features and improvements. We want share our progress with you out in the open in the coming weeks. Final release of v4 is scheduled for later this summer. 🚀
All licenses purchased in 2023 qualify as Kirby 4 licenses
We will treat any license bought on or after 1 Jan 2023 as if you bought it on the day of the v4 release. Older licenses that have not been activated yet will qualify as well. For older active licenses we will offer very fair upgrade prices as always.
Upgrades
Kirby 4 will be built upon the healthy code base we established for Kirby 3. Upgrades will be comparable to a 3.x release. While we stay on the same architecture, this new version will bring many long-awaited features and is going to move your projects forward.
Roadmap
ETA for Kirby 4
We roughly plan with the following schedule for v4. The success of the alpha & beta and your input will determine the final release date.
-
Alpha
May 2023
-
Beta
July 2023
-
Release
Late summer 2023
Alpha
We are not done yet …
This is the very first public test version of Kirby 4. Now it's time for you to get involved. We need your honest feedback and your eagle eyes to find bugs. We plan to build more features in the next weeks. You can follow our process on Discord.
This version is very likely buggy.
That's why we call it alpha :)
Do not use it in production!
Link field
Link to anything

fields:
link:
type: link

Page creation dialogs
Faster, better page creation

title: Product
create:
title:
label: Product name
fields:
- price
- brand
redirect: false
status: listed
Moving pages
I like to move it, move it
Section filters
Filter pages and files

Filter pages and files by any criteria: The pages and files sections now come with support for our powerful query string syntax.
expensive:
extends: sections/products
label: Expensive
query: page.childrenAndDrafts.filter('price', '>', '99')
cheap:
extends: sections/products
label: Cheap
query: page.childrenAndDrafts.filter('price', '<=', '99')
Color field
Color me surprised
Color in all its facets: Multiple color notations, color picker, pre-defined colors, custom color names and transparency. You can't get more color than that.

color:
type: color
options:
"Sunny rays": "#F8B195"
"First-love blush": "#F67280"
"Cherry blossom": "#C06C84"
"Morning gloom": "#6C5B7B"
"Midnight rain": "#355C7D"

Image focus
Focus Pocus

Never cut off the most important part of your images again. Set a focus point and let your images shine in all their beauty.
Uploader
Uploading files the smart way

Screenshot-123.jpg? With the preview and edit options in the new upload dialog, meaningless file names are now a thing of the past. Use the new blueprint options to optimize uploads before they land on the server.
title: Image
# Optimize on upload
create:
width: 500
height: 500
crop: true
Block field improvements
Our best field is now 25% bester

fields:
blocks:
type: blocks
fieldsets:
demo:
wysiwyg: true
preview: fields
tabs:
main:
fields:
title:
type: text
subtitle:
type: text
description:
type: writer
settings:
label: Settings
fields: # ...
Display and edit headings level inline

New toggles inside the drawer

Block splitting
- Split and merge text, list and headings
- New option buttons to split or merge
- Press enter at the end of a headline to append a new text block
- Text block with
inline: true
for text field will split directly on Enter (Shift + Enter
creates a hard line break) - Custom blocks can support splitting by implementing a
split
method
Minimized blocks while dragging

New Keyboard shortcuts
Action | Shortcut |
---|---|
Remove block | Meta + Backspace |
Move focus up | Meta + ↑ |
Move focus down | Meta + ↓ |
Move block up | Meta + Shift + ↑ |
Move block down | Meta + Shift + ↓ |
Extend selection up | Meta + Alt + ↑ |
Extend selection down | Meta + Alt + ↓ |
Split block | Meta + Enter |
Merge block | Meta + J |
Layout field improvements
Copy & Change
More flexibility: Simply change a layout, and copy one or all layouts into other layout fields.

Language editor
No longer lost in translation

Each language got its own view now. Easily configure your languages or edit language variables right from the Panel. Easy, right?
Search view
Deliver results

The larger your website, the more important becomes search. With Kirby's new search view, searching in the Panel has become a breeze.
Changeable file templates
Change is in the air

Change file templates to any allowed template defined by its parent in a files section or field.
Writer field improvements
Writing in style
With custom marks and nodes for your own style, customizable heading levels and a character counter, you have full control about your writing.


Link to internal pages & files with our new link field in the link dialog. No more guessing of URLs.
fields:
text:
type: writer
toolbar:
inline: false
marks:
- bold
- "|"
- link
fields:
text:
type: writer
headings:
- 2
- 3
- 4
fields:
text:
type: writer
marks:
- sub
- sup
- clear
fields:
text:
type: writer
minlength: 10
maxlength: 360
counter: false
Writer Plugins
You need a block or inline format that’s not available? Create your own marks and nodes with our brand new extension API for Writer plugins.
panel.plugin("acme/writer", {
writerNodes: {
blockquote: {
// ...
},
}
writerMarks: {
mention: {
// ...
},
},
};
Default page model
Set a new page standard
<?php
use Kirby\Cms\Page;
class DefaultPage extends Page
{
/**
* This method is now available for all pages
* unless they have their own page model.
*/
public function myCustomMethod(): string
{
return 'Hello world';
}
}
The default page model kicks in when there is no specific model for a page.
Of course, other models can extend the default page model, too.
Panel JavaScript API
Remote control
Our new Panel JavaScript API gives you access to the most important Panel features. Control dialogs, drawers, notifications and more from your plugins, your custom panel.js or even the console.

New Versioning Scheme
Semantic Versioning
{generation} | {major} | {minor} | {patch} |
---|---|---|---|
3 | 9 | 0 | 0 |
3 | 10 | 0 | 0 |
3 | 11 | 0 | 0 |
{major} | {minor} | {patch} | |
---|---|---|---|
3 | 9 | 0 | |
4 | 0 | 0 | |
5 | 0 | 0 |
Key points
- Kirby will follow semantic versioning
- Major versions will no longer be automatically bound to paid upgrades
- Major versions will be released on a yearly cycle to bring continuity and planning security.
Example roadmap
- 4.0.0: late summer 2023
- 5.0.0: late summer 2024
- 6.0.0: late summer 2025
Frequently asked questions
We are getting a lot of questions after our announcement and already tried to answer most of them here. We will keep collecting questions from the community here and try to answer them as transparently as possible. Let us know on Discord or in the Forum if you have additional questions.
What happens with licenses purchased before 2023?
We really appreciate the support of everyone who uses our volume discounts to purchase licenses in advance and we don't want to break the trust that comes with such purchases.
All unused v3 licenses, which have not been activated before 1.1.2023 will be upgraded to v4 for free as well.
What will happen with Kirby 3?
Kirby 3 will receive security support for quite a while after the launch of Kirby 4. The launch of Kirby 4 will not mean that you have to rebuild all your old projects.
The decision to build on the Kirby 3 architecture also means that you have a better chance for upgrades of old projects this time and we have a better chance to provide security support for Kirby 3 because it's not a completely different codebase.
What will Kirby’s future licensing model look like?
- No subscription
- One-time purchase that allows to keep using the purchased version
- Updates to a reasonable amount of new major versions will be included with each purchase
- More details to be announced
Changelog
Ch-ch-ch-changes
Core improvements
Support for UUIDs in URL helper
You can now pass a page UUID or a file UUID to the url()
helper and it will be converted to the actual URL:
<?= url('page://abcd') ?>
<?= url('file://abcd') ?>
This will also work for other URL methods like. $field->toUrl()
, Url:to()
, etc.
Support for UUIDs in API calls
You can now use UUIDs in API calls to request pages and files. #4769
/api/pages/@$pageUUID
The UUIDs must be passed without scheme, but a prefixed @
. I.e.:
// Nope
/api/pages/page://abcd
// Yes
/api/pages/@abcd
File routes can also handle file UUIDs for files and parents:
/api/pages/@$pageUUID/files/$filename
/api/pages/@$pageUUID/files/@$fileUUID
In addition to that there are new direct UUID file routes:
/api/files/@$fileUUID
More improvements
- New
File::blueprints()
method that collects valid blueprints from the files sections and files fields of the parent model - Fields can now define
'hidden' => true
(component notation) or::isHidden(): bool
(class-based) to make them non-rendering - New
File::changeTemplate()
method - New file permissions for
changeTemplate
- New
file.changeTemplate
hooks - New
Kirby\Email\Email::toArray()
andKirby\Email\Body::toArray()
methods - New
Kirby\Exception\AuthException
class
Panel improvements
Dialogs for fields
Fields can now define their own dialogs on the backend.
-
Fields with array definition:
return [ 'props' => [ // ... ], 'dialogs' => function () { return [ [ 'pattern' => 'delete', 'load' => function () {}, 'submit' => function () {}, ] ]; } ];
-
Field classes:
class MyField extends FieldClass { public function dialogs(): array { return [ [ 'pattern' => 'delete', 'load' => function () {}, 'submit' => function () {}, ] ]; } }
The route patterns for field dialogs are automatically prefixed with the following scheme:
dialogs/$modelPath/fields/$fieldName/$pattern
Here's an example:
dialogs/pages/blog/fields/blocks/delete
In a field component, the dialog can be opened by using the field endpoint:
this.$dialog(this.endpoints.field + "/delete")
New Icons
We've added new icons to our Panel iconset:
clear
split
merge
New Components
<k-code>
<k-dialog-body>
<k-dialog-box>
<k-dialog-buttons>
<k-dialog-fields>
<k-dialog-footer>
<k-dialog-form>
<k-dialog-notification>
<k-dialog-search>
<k-dialog-text>
<k-notification>
<k-drawer-body>
<k-drawer-fields>
<k-drawer-header>
<k-drawer-notification>
<k-drawer-tabs>
<k-tree>
<k-page-tree>
<k-browser>
<k-file-browser>
New helpers
$helper.focus(element)
$helper.object.length(object)
$helper.string.isEmpty(string)
$helper.url.base
$helper.url.buildQuery
$helper.url.buildUrl
$helper.url.isAbsolute
$helper.url.isSameOrigin
$helper.url.isUrl
$helper.url.makeAbsolute
$helper.url.toObject
New JS error classes
RequestError
JsonRequestError
More to come …
We are not done here …
Bug fixes
- Removed flickering from
k-pagination
when navigating - Fixed name of default blueprints to
pages/default
andfiles/default
- Blocks: batch selection allows deselecting blocks
- Blocks: batch selection gets deselected on Escape key
- Fixed i18n translate issues, e.g. for the user blueprint title #4869
- Writer field: Email mark toolbar button title is properly translated now
- Empty required Writer field now shows proper invalid styling in Panel
- UUIDs are less often generated when not needed to be generated
- Kirby queries now differentiate between integers and floats as arguments
- Kirby no longer hides errors in the response class when the response is converted to a string: #5027
Refactoring
Core
- Throw proper errors when thumbnails cannot be generated
- Removed
config/blueprints
directory with dead block blueprints and moved default blueprints for site, page and file to in-code arrays inKirby\Cms\Core::blueprints()
- Removed
Kirby\Toolkit\Properties
trait fromKirby\Api\Api
,Kirby\Cms\Api
,Kirby\Email\Email
,Kirby\Email\Body
,Kirby\Cms\Auth\Status
,Kirby\Cms\App
,Kirby\Cms\ContentTranslation
,Kirby\Cms\ModelWithContent
,Kirby\Toolkit\Pagination
,Kirby\Http\Uri
,Kirby\Cms\FileVersion
,Kirby\Filesystem\Asset
,Kirby\Filesystem\File
,Kirby\Image\Image
classes andKirby\Filesystem\IsFile
trait Kirby\Cms\Language
,Kirby\Cms\Plugin
,Kirby\Cms\Role
andKirby\Cms\StructureObject
don't extendKirby\Cms\Model
(and thus also don't use theKirby\Toolkit\Properties
trait) anymore
Panel
-
Cleaned up
k-toolbar
component -
Drawer Component Structure
<k-overlay type="drawer"> <form class="k-drawer" method="dialog"> <k-drawer-notification /> <k-drawer-header /> <k-drawer-body> <k-drawer-fields /> </k-drawer-body> </form> </k-overlay>
-
Portals: Dialogs, Drawers and other Overlays are now seperated into different portals. This is done by setting the overlay type:
<k-overlay type="dialog">
<k-overlay type="drawer">
<k-overlay>
An overlay without a type will use the default overlay portal. Separating them into different portals gives us more control over z-index and layering of elements.
-
Removed
v-model
fromk-form
andk-fieldset
-
Removed
$listeners
usage fromk-draggable
,k-button
and subcomponents,k-link
,k-headline
,k-form
,k-block
,k-block-title
,k-box
,k-image
,k-content-item
Deprecated
Core
Kirby\Cms\Model
in favor ofKirby\Cms\ModelWithContent
Kirby\Email\Email::clone()
andKirby\Email\Body::clone()
Panel
this.$events
: Usethis.$panel.events
instead.this.$panel.events.$on
: Usethis.$panel.events.on
insteadthis.$panel.events.$off
: Usethis.$panel.events.off
insteadthis.$panel.events.$emit
: Usethis.$panel.events.emit
insteadthis.$store.dispatch("isLoading")
: Usethis.$panel.isLoading
insteadthis.$translation
: Usethis.$panel.translation
insteadthis.$store.dispatch("dialog")
is deprecated. Usethis.$panel.dialog.open
andthis.$panel.dialog.close
insteadthis.$store.dispatch("drag", drag)
has been deprecated. Usethis.$panel.drag = drag
instead
Breaking changes
Removed deprecated code
Kirby\Form\Options
,Kirby\Form\OptionsApi
andKirby\Form\OptionsQuery
classes have been removed. UseKirby\Option\Options
,Kirby\Option\OptionsApi
orKirby\Option\OptionsQuery
instead.Kirby\Toolkit\Query
class has been removed. UseKirby\Query\Query
instead.- Passing the
$slot
or$slots
variables to snippets was deprecated and support has now finally been removed.
Core
- We have added more native PHP type hints throughout the system. When eextending core classes, this might require you to update your method to include those type hints as well.
- When thumb generation fails, the image API now throws an error and no longer loads the original image.
- Files:
manipulate
andfocus
are now used by core methods. They are no longer available for custom file methods and content fields with these names can only be accessed via e.g.$file->content()->get('focus')
. I18n::translate()
: If$fallback
is an array and neither the array$key
nor the array$fallback
have a matching entry for the locale, the first element of the$key
array will now be returned (not anymore the one from$fallback
). If$fallback
is a string, it will be considered with priority over both of these.- If overwriting the
hidden
field, it must return'hidden' => true
now Kirby\Uuid\Uuid
is now an abstract class, child classes need to implement theid
methodKirby\Uuid\Uuid::key()
can now also returnnull
if the new$generate
parameter isn't passed astrue
and no UUID has yet been generated for the model- Errors are no longer hidden when a response is converted to a string
Kirby\Cms\ModelWithContent
doesn't extendKirby\Cms\Model
anymore.- Removed the
::clone()
method fromKirby\Cms\Auth\Status
,Kirby\Http\Uri
,Kirby\Cms\FileVersion
,Kirby\Filesystem\Asset
,Kirby\Filesystem\File
,Kirby\Image\Image
,Kirby\Cms\Plugin
,Kirby\Cms\Role
,Kirby\Cms\StructureObject
andKirby\Cms\ContentTranslation
classes - Removed the
::hardcopy()
method fromKirby\Api\Api
,Kirby\Cms\Api
,Kirby\Email\Email
,Kirby\Email\Body
,Kirby\Cms\Auth\Status
,Kirby\Toolkit\Pagination
,Kirby\Http\Uri
,Kirby\Cms\FileVersion
,Kirby\Filesystem\Asset
,Kirby\Filesystem\File
,Kirby\Image\Image
,Kirby\Cms\Role
,Kirby\Cms\StructureObject
andKirby\Cms\ContentTranslation
classes - Removed
::site()
method fromKirby\Cms\Plugin
,Kirby\Cms\Role
andKirby\Cms\StructureObject
classes - Removed
::kirby()
method fromKirby\Cms\Role
andKirby\Cms\StructureObject
classes - Unauthenticated API and Panel calls now return a correct 401 HTTP code instead of 403.
Panel
this.$library.autosize
has been removed, wrap<textarea>
elements inside<k-autosize>
element instead.- Defining the footer slot in
<k-dialog>
will no longer wrap the slot content in the<footer>
element. This can now be more flexibly handled by using<k-dialog-footer>
inside the slot. - The form drawer no longer automatically closes on submit. This is introducing the same behaviour as in dialogs. Auto-closing might often not be the intended result of submitting the form and it's easier to close it manually in a submit handler than to re-open it again.
this.$store.state.isLoading
is no longer available. You can now usewindow.panel.isLoading
orthis.$panel.isLoading
in Vue components to access the current loading state.this.$store.state.dialog
is no longer available. Usethis.$panel.dialog
instead.- Removed
this.$store.state.drag
, usewindow.panel.drag
/this.$panel.drag
instead. k-button
,k-link
,k-headline
andk-content-item
only emit theclick
event. For other native events, use the.native
event listener modifier- Native events (e.g.
click
,dbclick
) need the.native
modifier now when used onk-block
andk-block-title
- Need to use
.native
modifier for all previous event listeners onk-box
andk-image