🤩 Highlights
- 🐘 PHP 8.5 support
- 📻 CORS support
- ☑️ New
batchselect mode for the checkboxes field - ↕️ Collapse / expand all blocks
- 📂 New
createoption for the files section - 🏷️ Better auto-labels in the Panel
- ⚡️ Big performance boost for forms
🎉 Features
PHP 8.5 support
PHP 8.5 has been released a few days ago and it's a fantastic release (https://www.php.net/releases/8.5/en.php) Kirby 5.2 is already fully compatible. #7671
Automatic handling of CORS (Cross-Origin Resource Sharing)
For headless setups, proper CORS support is incredibly helpful to speak to your Kirby API, content representations or KQL across domains. Thanks to the help of @johannschopplich, Kirby is now making this very easy out of the box.
How It Works
- Simple setup: Set
'cors' => trueto enable CORS with sensible defaults. Check below for additional configuration options. - Automatic preflight handling: When CORS is enabled, all
OPTIONSCORS preflight requests automatically receive a204 No Contentresponse with appropriate CORS headers. - Header injection: CORS headers are lazily injected into the
Responder::headers()method for all responses. Custom headers set by plugins or user code are never overridden. - Vary header management (Hono inspired):
- Wildcard origins (
*): NoVary: Originheader is added (cache-efficient since response is identical for all origins) - Specific origins: Automatic
Vary: Originheader ensures different origins are cached separately - Header reflection: Automatic
Vary: Access-Control-Request-Headersfor preflight requests when reflection is enabled - Auth/Cookie tracking: Automatic
Vary: Authorization, Cookiewhen response uses authentication or cookies - Smart merging: All Vary values are combined intelligently without duplication
- Wildcard origins (
- Header reflection opt-in: With
'allowHeaders' => true, preflight requests mirror the headers requested by the client whileVary: Access-Control-Request-Headersensures correct caching. The default[]remains secure-by-default by omitting the header.
Configuration
CORS can be enabled in three ways:
1. Boolean (uses all defaults)
return [
'cors' => true
];2. Array (custom configuration)
return [
'cors' => [
'allowOrigin' => 'https://example.com',
'allowCredentials' => true
]
];3. Closure (dynamic/request-based)
return [
'cors' => function ($kirby) {
$origin = $kirby->request()->header('Origin');
// Allow specific origins with credentials
if (in_array($origin, ['https://app1.com', 'https://app2.com'])) {
return [
'allowOrigin' => $origin,
'allowCredentials' => true,
'allowMethods' => ['GET', 'POST']
];
}
// Fallback to wildcard for other origins
return ['allowOrigin' => '*'];
}
];Note: Setting 'cors' => [] (empty array) is equivalent to 'cors' => true and enables CORS with defaults. To disable CORS, use 'cors' => false or omit the option entirely.
Available Options
| Option | Type | Default | Description |
|---|---|---|---|
allowOrigin
| string, array
| '*'
| Allowed origins (e.g., '*', 'https://example.com', or ['https://app1.com', 'https://app2.com'] for multiple origins)
|
allowMethods
| string, array
| ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
| Allowed HTTP methods for preflight requests |
allowHeaders
| string, array, bool
| []
| Allowed request headers. [] (default) suppresses Access-Control-Allow-Headers; true reflects headers from Access-Control-Request-Headers; strings/arrays allowlist specific headers explicitly
|
maxAge
| int
| null
| Preflight cache duration in seconds. null uses the browser default (typically 5 seconds)
|
allowCredentials
| bool
| false
| Allow requests with credentials (cookies, auth). Cannot be true with wildcard origin
|
exposeHeaders
| string, array
| []
| Response headers exposed to the browser |
Security considerations:
- Enabling CORS allows external origins to interact with your Kirby site. This means that sites hosted on external origins (domains) can access or control your Kirby site via the browser of their visitors. Only enable CORS when needed, and restrict the configuration (origins, methods, headers) to the minimum required for your use case.
'allowCredentials' => truelets browsers include cookies and HTTP authentication with cross-origin requests, effectively granting the external origin the same permissions as the logged-in user. Only enable credentials when the requesting origin is fully trusted and under your control.
New batch select mode for the checkboxes field
You can now switch on the select all/deselect all toggles with the new batch option in the checkboxes field to select or deselect multiple checkboxes at once. This is helpful when a checkboxes field has a lot of options https://feedback.getkirby.com/81 #7659
fields:
countries:
type: checkboxes
options:
# lots of countries
batch: truecheckboxes.mp4
Thanks to this new feature, we now also have the following additions:
- New
select-allanddeselect-allicons - New
select.all,deselect.allanddeselecttranslation strings
Collapse / expand all blocks
There are new options to expand or collapse all blocks at once in the blocks options dropdown (if there are collapsible blocks used, e.g. with preview: fields) (thx @dennisbaum) #7555
New create option for the files section
The new create option for the files section lets you disable the upload button. This is useful if you are using queries in files sections and the upload would not actually show up in the list. It is comparable to the create option in the pages section. #7649
sections:
images:
type: files
query: page.images.filter('customField', 'customFilter')
create: falseBetter auto-labels in the Panel
We've improved our auto-labelling method for labels, titles and more in various places of the Panel. Here's an example from a blueprint #7656:
sections:
coverImages:
type: filesThe files section above does not define a label, but already has a meaningful name, which can now be turned into one. In previous releases coverImages would have been turned into the auto-label Coverimages. Not that nice. Now camel casing automatically turns it into Cover images. This also works if you prefer kebab-casing.
sections:
cover_images:
type: filesThis will very often save you from defining additional label/title/name options. This can now help you in the following places:
- Blueprint title
- Blueprint tab labels
- Section headlines
- Form field labels
- Block field: fieldset names
- Block field: fieldset tab names
- Block field: fieldset group names
- Role titles
- Custom panel search labels
This feature is based on our new Kirby\Toolkit\Str::label() method, which you can of course use in your own code, to create nice auto-labels.
⚡️ Performance
We've managed to achieve massive performance improvements for forms. In our test setup we measure two different scenarios:
1. Generating the backend props for all fields and their custom configurations.
5.1.4: 00:06.198
5.2.0: 00:03.045
≈ 50.9% reduction in time
≈ 2.04× speed-up
2. Generating the backend props for all our field related panel views.
5.1.4: 00:19.390
5.2.0: 00:05.900
≈ 69.6% reduction in time
≈ 3.29× speed-up
Our test sandbox is of course a very theoretical environment, but we still expect to see some serious improvements in real-world setups.
The following steps lead to this improvement:
- New ::emptyValue() method for Kirby\Form\Field and Kirby\Form\FieldClass, which can be overwritten to define the preferred empty value when the field is being reset #7663
- New Kirby\Form\Field::fillWithEmptyValue() and Kirby\Form\FieldClass:reset(), which are used in Kirby\Form\Fields to reset field values without evaluating computed props again if not
necessary. This is the part that leads to the performance improvements. #7663
- Field options are now cached in memory to improve the performance of fields. #7664
- New protected BlocksField::fieldsetForm method to cache forms for each fieldset type. #7665
- Fixes: #7641 and #6734
We've also added a new in-memory snippet cache to speed up snippet lookups and avoid disk access #7369
✨ Enhancements
- Plugin links point to
plugins.getkirby.comdirectory if plugin is listed there #7243 - View button:
optionsstring supports Kirby query syntax #7566 k-dropdown-itemsupports all button props (esp.dialoganddrawer) #7590- New
Kirby\Http\Uri::inherit()methods that copies over query, params and fragment from another URL #7611 - New
Kirby\Reflection\Constructorclass #7620
use Kirby\Reflection\Constructor;
class Reflectable
{
public function __construct(
protected $a,
protected $b
) {
}
}
$constructor = new Constructor(Reflectable::class);
$constructor->getAcceptedArguments([
'a' => 'Test A',
'b' => 'Test B',
'c' => 'Test C'
]);
// will keep 'a' and 'b'
$constructor->getIgnoredArguments([
'a' => 'Test A',
'b' => 'Test B',
'c' => 'Test C'
]);
// will keep 'c'- The PHP version check can now temporarily be disabled with a new
KIRBY_PHP_VERSION_CHECKconstant. This is for very specific cases and comes with risks, please note the code comment inbootstrap.php. #7648 - New
Kirby\Image\Gravityenum #7732 t()helper accepts array for$fallback#7717
🐛 Bug fixes
- Blocks field: when pasting blocks into a nested blocks field it is now ensured that only one blocks field receives the pasted content, not nested and parent blocks field both. #7093
- Fixed query support for
defaultoption of the number, date and time fields #7594 - The
RequestErrorJS class is polyfilling error messages stored in the error prop instead of the message prop to create more consistency. #7670 - The
RequestErrorJS class is now responsible for all server errors to add another layer of consistency there. #7670 - The
detailsarray from custom Kirby Exceptions is now always properly passed forward in all error responses. #7670 - The
panel.notification.error()method is now also passing details forward to the error object. #7670 - Fixed cropping with
imagickthumb driver #7732 - Handle malformed images gracefully on upload #7724 (thanks to @Stalin-143)
Kirby\Image\Image::imagesize()now returns array|falseKirby\Image\Dimensions::forImage()returns 0x0 for invalid imagesKirby\Image\Image::isResizable()checks for zero dimensions early
♻️ Refactored
k-buttonsupports dialog/drawer objects as props #7590$panel.dialog.open()/$panel.drawer.open()accept a single object argument if it has aurlkey #7590- Code quality fixes #7678
🧹 Housekeeping
- Removed use of old PHP functions that are deprecated in PHP 8.5 #7556
- Removed PHPBench tests in favor of Sandbox CLI bench setup #7667
- Running PHP CS Fixer in parallel now #7677
- Upgraded to Psalm 6 #7672
- New error handling examples in the lab (
/panel/lab/internals/errors) for various scenarios (opening views, dialogs, drawers, sending requests, etc.) #7670 - Upgraded PHP dependencies
- Upgraded JS dependencies