🎉 Features
Local development licenses
A Kirby site can now be activated as development site when you confirm to comply with the license terms for free development licenses. When the site is not set up locally, Kirby will regularly check that your development site is not publicly accessible. #7832
Frontmatter Data Handler
New Frontmatter Data handler, which can be used to import or export frontmatter into Kirby. #8019
Decoding
use Kirby\Data\Frontmatter;
$md = <<<MD
---
title: My new article
date: '2026-03-12'
---
# Hello world
This is my brand new article written in **Frontmatter/Markdown**
MD;
$result = Frontmatter::decode($md);
// [
// 'title' => 'My new article',
// 'date' => '2026-03-12',
// 'text' => '# Hello world ...'
// ]Encoding
use Kirby\Data\Frontmatter;
$result = Frontmatter::encode([
'title' => 'My new article',
'date' => '2026-03-12',
'text' => '# Hello world …'
]);
// ---
// title: My new article
// date: 2026-03-12
// ---
// # Hello world …New translations
- Japanese (thanks to Kosuke Okahara)
- Ukranian
✨ Enhancements
- SmartyPants is directly using UTF-8 characters instead of HTML entities
- A page/site/user/file will now automatically be unlocked, as soon as the editor leaves the view or closes the tab/window. This will make sure that content-editing is not unnecessarily blocked for other editors. #7975
- New
Kirby\Content\Version::unlock()method - New
panel.content.unlock()method - New
/api/(:all)/changes/unlockroute
- New
- License dialog: Show registered domain and link to license terms #7832
- You can now use
this.$helper.string.sanitizeHTML()in the Panel to create a clean HTML string with configurable marks and nodes. By default, the sanitizer will only keep inline marks (bold, code, italic, link, strike, sub, sup, underline) #7922To set custom writer marks and nodes, you can pass an options object:this.$helper.string.sanitizeHTML('<p><marquee><strong onclick="alert(\'boo\')">Foo</strong></marquee></p>') // returns: <strong>Foo</strong>
this.$helper.string.sanitizeHTML(unsanitizedHTML, { marks: ["bold"], nodes: ["doc", "praagraph", "text")] });
- It's now possible to define any extension directly in your config, to be able to set site-specific extensions without creating a helper plugin. #8005
// /site/config/config.php return [ 'extensions' => [ 'pageModels' => [ // ... ], 'blueprints' => [ // ... ] ] ];
- Link field: reduced the amount of backend request fired directly when mounted (thx @tobimori) #8072
- Added some missing common/modern extensions to
Kirby\Filesystem\F::$typesandKirby\Filesystem\Mime::$types. #8134 - New
Kirby\Toolkit\A::flip()method #8132 - Increased length of randomly generated chunk IDs for chunked uploads to reduce risk of filename collisions #8182
- New
Kirby\Filesystem\F::update($file, $callback)method that reads and updates a file under lock #8187 - Improved error handling for block snippet errors rendered via
Kirby\Cms\Block::toHtml()by logging suppressed errors and escalating exceptions in debug mode #8085. - Added a translatable default placeholder for the tags field input #6114
🏎️ Performance
- Improved performance of several system methods:
Kirby\Filesystem\Dir::size()#8131Kirby\Filesystem\Mime::fromSvg()#8130Kirby\Filesystem\Mime::toExtension()andKirby\Filesystem\Mime::toExtensions()#8133Kirby\Toolkit\Controller#8143Kirby\Toolkit\Html::attr()andKirby|Toolkit\Xml::encode()#8144Kirby\Toolkit\Str::similarity(),Kirby\Toolkit\Str::startsWith(),Kirby\Toolkit\Str::endsWith(),Kirby\Toolkit\Str::encode(),Kirby\Toolkit\Str::ascii()#8138 #8147 #8145 #8158- Excluded keys lookup set in
Kirby\Toolkit\A::without()#8146
🐛 Bug fixes
- Structure field previews: fields with HTML content (e.g. list, writer) do not expand the table with all their content anymore #7918
- Fixed URL validation for URLs with multiple hyphens #7942
- Redis cache driver: Fixed flushing all databases even when using a prefix. Now only the Reds database for the cache with its prefix gets flushed. #8148
- Fixed
minWords/maxWordsvalidators for non-single whitespace #8141 - Handle
Dir::remove()with atomic rename #6266 panel.request.body(null)does not send"null"to the backend as the request body anymore #8016- Unset request headers aren't sent as
"false"to the backend anymore but instead omitted from the request. #8016 - Fixed handling of partial step config arrays in
Kirby\Toolkit\Date Kirby\Toolkit\LayzValue: fixed passing variadic arguments #8139Kirby\Toolkit\A::implode(): preserve falsy values when imploding array #8140- Fixed fractional support for
Kirby\Toolkit|Str::toBytes()#8159 - Blocks field: Fixed batch delete #8206
- Panel system view: The warning for the default cookie key is no longer displayed if a custom cookie key was set via the
cookie.keyoption - Panel: Fixed failing to load when a stricter Content-Security-Policy with nonce-based script-src is used. #8208
I18n::template(): Prevent TypeError when$replaceis null #8199- Kirby queries: Fixed tokenizer position desync on multibyte characters (@thx @LaurentTacco) #8203
- Fixed a content lock deadlock in multilingual setups where saving a secondary language while another user holds the lock on the primary language would cause both users to see the page as locked by the other. #8099
- Fixed file sorting when a secondary language is active #8212
- Fixed Cmd/Ctrl+S in textarea toolbar dialogs inserting
[object Object]instead of the formatted link/email text. #7934 - Disabled unselected model picker options once the field’s
maxlimit is reached #8232 - Localize license hub link #8239
☠️ Deprecated
- Using arbitrary custom permissions without registering them first via the
permissionsextension has been deprecated. Those will be ignored in a future release, so make sure to define your custom permissions via thepermissionsextension. #8070 Kirby\Text\KirbyTag::option()andKirby\Text\KirbyTag::$options: Use$tag->kirby()->option()instead. #8196Kirby\Text\KirbyTags::parse():$optionsparameter. Use new$debugparameter instead. #8196
♻️ Refactored
- New
Kirby\Cms\LicenseStatus::AcknowledgedandKirby\Cms\LicenseType::Freecases #7832 $helper.string.hasEmoji(): simplified regular expression #7966- Started refactoring some of our JavaScript code as TypeScript for increased type-safety #7973
- Migrated helpers to Typescript:
$helper.array#7977,$helper.regex#7976 $helper.debounce/throttle#7980,$helper.isComponent#7982,$helper.embed#7983,$helper.keyboard#7984,$helper.isUploadEvent#7985,$helper.queue#7981,$helper.file#7990,$helper.field#7989,$helper.string#7979,$helper.link#7994,$helper.focus#7991,$helper.url#7992,$helper.page#7995,$library.colors#7997,$helper.clipboard#7988,$helper.upload#7996,dayjsplugins #8009,$helper.sort#7993- Migrated
HistoryandTimeras helper class to TypeScript #8010 #8011 - Migrated first Panel state modules to TypeScript #8012 #8020 #8015, #8016, #8028, #8027, #8033, #8030, #8031, #8032, #8029, #8034, #8039, #8038, #8041, #8043, #8042
- Types on
this.$library,this.$helperandthis.$escare exposed in Vue SFC #8017
- Migrated helpers to Typescript:
- New
RedirectErrorerror class with support inpanel.error()#8016 - Refactored Permissions (thanks to @lukaskleinschmidt) #8004
- Renamed
Permissions::$actionsproperty to$defaults; added separate protected array$actionspopulated by the constructor - Replaced 6 old methods (
Permissions::setAll,Permissions::setCategory,Permissions::setCategories,Permissions::setAction,Permissions::hasAction,Permissions::hasCategory) with 4 cleaner ones:Permissions::normalize(),Permissions::expand(),Permissions::get(),Permissions::has() Permissions::normalize()andPermissions::expand()handle all four input shapes:null,bool,['cat' => bool],['cat' => ['action' => bool]], including wildcard at both levels.Permissions::for()now deprecates$category = nullviaHelpers::deprecated()and throwsLogicExceptionif a stored value is somehow not a bool- Slightly refactored the Blueprint class:
array_fill_keys(array_keys($defaults), false)instead ofarray_map(fn () => false, $defaults).
- Renamed
- Ensure not wrapping
<div>tags with<span>tags in<k-input>#8018 - New
Kirby\Template\Snippet::hasSlots()method #8178 - Guard Slots in Snippets from being double-opened #8179
- New
$debugparameter inKirby\Text\KirbyTags::parse()#8196 - Clarify
Html::rel()use #8142
🚨 Breaking changes
Kirby\Cms\Block::toHtml()now throws block snippet exceptions in debug mode instead of returning an inlineBlock errormessage. While this is technically a breaking change, we see this as a debugging enhancement which can clearly go into a minor release without negative impact for real-world projects.
🧹 Housekeeping
- Extended our contributing guidelines #7949
- Attaching files for local Lab to each release #7951
- Use
cpxfor pinned composer dependencies shared between local dev and CI - Replace
jsdomwithhappy-domfor frontend unit tests #7971 - New Unit tests for the Permissions class #8004
- New Unit test for the new option normalization in the Blueprint class #8004
- Upgraded Parsedown library #8213