Description
When you edit an image with the asset Image Editor from inside a CKEditor field, _reloadImage() in src/web/assets/ckeditor/src/image/imageeditor/imageeditorcommand.js builds a cache-busted src from srcInfo.baseSrc. But baseSrc comes from the greedy (.*) in _srcInfo()'s regex:
const match = src.match(/(.)#asset:(\d+)(?::transform:([a-zA-Z][a-zA-Z0-9_]))?/);
// baseSrc = match[1] → everything before "#asset:", INCLUDING any existing query
So baseSrc already contains the previously-appended ?, and _reloadImage() appends another one each time:
let newSrc = image.srcInfo.baseSrc + '?' + new Date().getTime() + '#asset:' + image.srcInfo.assetId;
The query string stacks on every edit, producing malformed URLs (a URL can only have one ?):
…/MT-3_2026-06-09-020259_hbsn.jpeg?0?1780971727673?1780971779690?1780971926837#asset:1395663
This stacked URL is then persisted into the stored field content.
A second, related problem: even with a single valid cache-buster, the editor swaps the src immediately on save with no allowance for CDN propagation. When assets are served through a CDN that caches by path / doesn't vary on the query string and invalidates asynchronously (e.g. CloudFront + AWS Serverless Image Handler, which is a very common Craft setup), the reload fetches the still-cached pre-edit image, so the edit doesn't visibly update — often requiring repeated saves or a hard refresh.
For reference, Craft core avoids this for its own thumbnails by deriving a stable, content-based buster from the asset's modified time (AssetsHelper::revUrl() / revParams() → ?v=), rather than appending a fresh timestamp onto whatever is already in the src.
imageeditorcommand.js
Steps to reproduce
- Insert an image asset into a CKEditor field and save the entry.
- Select the image → open the Image Editor → crop/rotate → Save.
- Repeat step 2 two or three more times.
- View the image src (editor "Source" view or the saved field content): the query string accumulates — ???… — instead of being replaced.
- (If assets are behind a CDN like CloudFront/Serverless Image Handler) the edited image also fails to display the change until multiple saves / a hard refresh, because the stale CDN copy keeps being served.
Additional info
Craft version: 5.10.5
PHP version: 8.2.28
Database driver & version: MySQL 8.0.40
Plugins & versions:
CKEditor (craftcms/ckeditor), 5.x branch (CKEditor 5, ckeditor5 ^48.2.0)
AWS S3 (craftcms/aws-s3) 2.3.0 — assets on S3 served via CloudFront + Serverless Image Handler
Description
When you edit an image with the asset Image Editor from inside a CKEditor field, _reloadImage() in src/web/assets/ckeditor/src/image/imageeditor/imageeditorcommand.js builds a cache-busted src from srcInfo.baseSrc. But baseSrc comes from the greedy (.*) in _srcInfo()'s regex:
const match = src.match(/(.)#asset:(\d+)(?::transform:([a-zA-Z][a-zA-Z0-9_]))?/);
// baseSrc = match[1] → everything before "#asset:", INCLUDING any existing query
So baseSrc already contains the previously-appended ?, and _reloadImage() appends another one each time:
let newSrc = image.srcInfo.baseSrc + '?' + new Date().getTime() + '#asset:' + image.srcInfo.assetId;
The query string stacks on every edit, producing malformed URLs (a URL can only have one ?):
…/MT-3_2026-06-09-020259_hbsn.jpeg?0?1780971727673?1780971779690?1780971926837#asset:1395663
This stacked URL is then persisted into the stored field content.
A second, related problem: even with a single valid cache-buster, the editor swaps the src immediately on save with no allowance for CDN propagation. When assets are served through a CDN that caches by path / doesn't vary on the query string and invalidates asynchronously (e.g. CloudFront + AWS Serverless Image Handler, which is a very common Craft setup), the reload fetches the still-cached pre-edit image, so the edit doesn't visibly update — often requiring repeated saves or a hard refresh.
For reference, Craft core avoids this for its own thumbnails by deriving a stable, content-based buster from the asset's modified time (AssetsHelper::revUrl() / revParams() → ?v=), rather than appending a fresh timestamp onto whatever is already in the src.
imageeditorcommand.jsSteps to reproduce
Additional info
Craft version: 5.10.5
PHP version: 8.2.28
Database driver & version: MySQL 8.0.40
Plugins & versions:
CKEditor (craftcms/ckeditor), 5.x branch (CKEditor 5, ckeditor5 ^48.2.0)
AWS S3 (craftcms/aws-s3) 2.3.0 — assets on S3 served via CloudFront + Serverless Image Handler