Full Changelog: 0.4.0...0.5.0
[0.5.0] - October 02, 2026
Fixed
- Bug: The
imagex-picture-jsonsnippet crashed withCall to undefined function transformForJson(): the helper lives in theTimNarrnamespace, but the snippet called it unqualified. It now imports the function. - Bug: Art-direction styles applied the wrong
aspect-ratio/object-positionwhen severalmediaqueries matched at once (e.g.(min-width: 1200px)followed by(min-width: 600px)).<picture>uses the first matching<source>, but CSS applies the last matching rule, so the later source's values won. Rules are now emitted in reverse order, and once a property is overridden by any source, every source sets it — including sources that keep the default, which previously inherited another source's value. - Bug: With
customLazyloading: true, eager images (loading: 'eager') were rendered without asrcattribute — and, combined withnoSrcsetInImg: true, without any image URL at all. Eager images now always getsrc. - Bug:
relativeUrls: trueproduced broken or absolute URLs on subfolder installs and multi-language sites. It stripped the fullsite()->url()— which includes the subfolder (/blog/media/…became/media/…) and, on multi-language sites, the language prefix (so nothing matched). Only the origin of Kirby's index URL is stripped now (newgetUrlOrigin()helper). Hosts that merely share the prefix (example.com.cdn.net) are no longer mangled. - Bug: Kirby's shorthand srcset presets (
'default' => [400, 800]or[400 => '1x', 800 => '2x']) crashed withCannot use a scalar value as an array. They are now normalized via the newnormalizeSrcsetPreset()helper, which also throws a descriptive error for empty presets or entries without a positivewidth. ratio: 'intrinsic'on an image without readable dimensions (e.g. SVG) now throws a descriptiveInvalidArgumentExceptionnaming the file instead of a bareDivisionByZeroError.- Both snippets now report a missing
imagewith the plugin's ownMissing required option: imageerror instead of a preceding PHP "undefined variable" warning. - Bug: Flat
attributesonartDirectionentries (e.g.['data-x' => 'y']) were dropped silently with a PHP warning, andclass/stylestrings threw a type mismatch error. They are now normalized likeattributes.img/picture/sources, so flat and structured attributes as well asclass/stylestrings work everywhere. - An invalid ratio like
'0/9'threw a genericKirby\Exception\Exception. It now throwsInvalidArgumentExceptionlike every other validation error. Ratio errors include the offending value, and forartDirectionentries also the entry index (e.g.artDirection[0]: Invalid ratio format '16:9'). Attribute errors onartDirectionentries carry the index too. - Bug: A user-supplied
<img>idstarting with a digit (e.g. a UUID) or containing characters like.or:produced an invalid#idselector in the art-direction styles, which browsers silently dropped. The id is now escaped via the newescapeCssIdentifier()helper (CSSOMCSS.escape()semantics, plus hex-escaping</>so it can't close the<style>element). - Bug: For images that already are in one of the configured
formats(e.g. a WebP upload withformats: ['avif', 'webp']), the<img>used the format preset (my-srcset-webp) instead of the base preset (my-srcset), including its quality settings. Srcset presets are now resolved by preset name, so the<img>always uses the base preset, as documented.
Added
- New
noncesnippet option for strict Content Security Policies. It is added to the generated<style>element, and thefocusstyles move from the<img>'sstyleattribute into that element as an#idrule (CSP nonces don't coverstyleattributes). NewImagex::getNonce()getter. - Integration tests for the
Imagexclass (tests/imagexTest.php), running against a real Kirby instance with generated fixture images.tests/bootstrap.phpnow registers the plugin once for the whole run.
Changed
- BREAKING: Unknown options now throw an
InvalidArgumentExceptionlisting the allowed ones. This covers top-levelImagexoptions (e.g.ration) andattributeskeys other thanimg,pictureandsources(e.g.image). Previously both were ignored silently. Migration: fix the misspelled key. A misspelled snippet option is still ignored, because the snippets only forward known options. - Invalid
class/styleattribute types (e.g. an integer) now throw when theImagexinstance is created rather than when its attributes are read. - The option defaults now live in the
Imagexclass only, so the snippets no longer repeat them.new Imagex(['image' => $file])now works with the same defaults as the snippets, and options passed asnullfall back to the default. Previouslyratio,srcsetandcompareFormatswere required when using the class directly. - The helper functions in the
TimNarrnamespace, as well asImagex::getSmallestFormatForImage(), are now marked@internal. They are not part of the public API and may change in any release. The README has a new "Public API" section listing what is covered by semantic versioning. - BREAKING: Attributes that mix flat keys with
shared/eager/lazykeys (e.g.['class' => 'x', 'lazy' => [...]]) now throw anInvalidArgumentException. Previously the flat keys were dropped silently. Migration: move the flat keys intoshared. - BREAKING:
artDirectionentries are validated at construction time. Each entry must have a non-emptymediastring (a<source>withoutmediaalways matches, so the default image was never used), only the keysmedia,ratio,imageandattributesare accepted,imagemust be aKirby\Cms\Fileornull(still falls back to the main image), andratiomust be valid. Migration: add amediacondition to every entry and fix typos in keys. - The main
ratiois now validated at construction time instead of on first render. - BREAKING: The
relativeUrlsplugin option must be a boolean. A non-boolean value (e.g.1or a string fromenv()) was previously coerced silently and now throws anInvalidArgumentException. Migration: set it totrueorfalse. - The plugin options
formats,compareFormatsWeights,customLazyloading,addOriginalFormatAsSource,noSrcsetInImgandrelativeUrlsare type-checked at construction time, throwing a descriptiveInvalidArgumentExceptioninstead of aTypeError. - All srcset preset errors (missing
thumbs.srcsets, missing base or format preset, malformed preset) and thecompareFormats"not enough formats" error are now thrown at construction time asInvalidArgumentException. Previously a missingthumbs.srcsetsconfig crashed with aTypeError, and some errors only surfaced during rendering. - All PHP files declare
strict_types, enforced by php-cs-fixer'sdeclare_strict_typesrule.
Performance
- Ratio-based srcset presets are memoized per
Imagexinstance instead of going through the persistent cache. They were resolved for the<img>, every format and every art-directed source — each time re-validating the config, JSON-encoding and hashing it, and reading a cache file for what is plain arithmetic. Existingsrcset-config-*cache entries are no longer used and can be removed by flushing the plugin cache. - The
compareFormatsresult is memoized per image and ratio on top of the persistent cache; it was looked up once per format for every art-directed source. - Srcset presets are resolved, normalized and validated once in the constructor (previously checked twice and rebuilt on every call).
Removed
- BREAKING:
Imagex::getSmallestFormat(). It was an unused wrapper aroundgetSmallestFormatForImage()without arguments. Migration: callgetSmallestFormatForImage()instead (now@internal). - BREAKING:
srcHandler()helper. Its only job — droppingsrcfor custom lazy loading — is now a plain default attribute inImagex::getImgAttributes(); user-suppliedsrcoverrides are handled bymergeHTMLAttributes()as for every other attribute. Migration: none for snippet users; if you called it directly, setsrcviaattributes.imginstead.
Migration Guide
From 0.4.x to 0.5.0
Most setups need no changes. 0.5.0 validates options up front and throws where 0.4.x silently ignored mistakes, so an upgrade surfaces misconfigurations that were already there. Go through these points; each error message names the offending option and the valid alternatives.
1. Add a media condition to every artDirection entry
An entry without media always matched, so the default image was never shown. It now throws.
// Before
'artDirection' => [
['ratio' => '21/9', 'image' => $wideImage],
],
// After
'artDirection' => [
['media' => '(min-width: 800px)', 'ratio' => '21/9', 'image' => $wideImage],
],Only the keys media, ratio, image and attributes are accepted; a typo like ratios now throws instead of being ignored.
2. Don't mix flat and structured attributes
Flat keys next to shared/eager/lazy were dropped silently. Move them into shared:
// Before
'attributes' => [
'img' => [
'class' => 'hero',
'lazy' => ['data-sizes' => 'auto'],
],
],
// After
'attributes' => [
'img' => [
'shared' => ['class' => 'hero'],
'lazy' => ['data-sizes' => 'auto'],
],
],3. Fix unknown option and attributes keys
attributes only accepts img, picture and sources (e.g. image throws). When you use the Imagex class directly, misspelled options like ration throw too. Misspelled snippet options are still ignored.
4. Set relativeUrls to a real boolean
// Before
'timnarr.imagex.relativeUrls' => env('RELATIVE_URLS'), // e.g. '1'
// After
'timnarr.imagex.relativeUrls' => env('RELATIVE_URLS') === '1',5. Only if you call PHP internals directly
Imagex::getSmallestFormat()was removed. UsegetSmallestFormatForImage().- The
srcHandler()helper was removed. Setsrcviaattributes.imginstead. - The helper functions in the
TimNarrnamespace andImagex::getSmallestFormatForImage()are now@internaland may change in any release. See "Public API" in the README for what is covered by semantic versioning.