You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have added steps to test this contribution in the description below
If there's an existing issue for this PR then this fixes#23282 (in part)
Description
Umbraco does not automatically append cache busting parameters to raw media URLs. This is by design, to prevent cache misses downstream.
This does however come with a few drawbacks, as described in ##23282.
One would then assume that a custom Media URL provider would be able to solve these drawbacks for a concrete implementation --- but unfortunately, the rich text rendering assumes a bit too much when rendering local image sources; it does not expect Media URLs with query string parameters.
In effect, if a Media URL provider yields Media URL with a cache buster parameter (e.g. /media/something/image.png?rand=123), the rich text conversion ends up generating Media URLs with two ? parts (/media/something/image.png?rand=123?rmode=max&....).
This PR ensures that the rich text rendering appends query string parameters safely, thus enabling Media URL providers with cache busting by query string.
Note
Crop URLs are cache busted by default as-is, because the effective crops are expected to change over time.
Testing this PR
Replace the default Media URL provider with a custom implementation which adds cache busting (example code below), and verify that:
Rich text rendering produces valid Media URLs which include the cache busting part.
Media picker "bare" URLs contain the cache busting part.
Media picker crop URLs also contain the cache busting part (despite them already having a cache buster by default).
Verify all of the above both for templated rendering (example template below) and for the Delivery API output.
usingUmbraco.Cms.Core;usingUmbraco.Cms.Core.Composing;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Core.PropertyEditors;usingUmbraco.Cms.Core.Routing;namespaceMy.Site.Providers;publicclassMyMediaUrlProvider:DefaultMediaUrlProvider{publicMyMediaUrlProvider(MediaUrlGeneratorCollectionmediaPathGenerators,IUrlAssemblerurlAssembler):base(mediaPathGenerators,urlAssembler){}publicoverrideUrlInfo?GetMediaUrl(IPublishedContentcontent,stringpropertyAlias,UrlModemode,string?culture,Uricurrent){UrlInfo?urlInfo=base.GetMediaUrl(content,propertyAlias,mode,culture,current);if(urlInfo?.Urlisnull){returnurlInfo;}// assemble a new Media URL with a calculated cache bustervarcacheBuster=content.UpdateDate.Ticks;varurl=newUri($"{urlInfo.Url}?r={cacheBuster}",urlInfo.Url.IsAbsoluteUri?UriKind.Absolute:UriKind.Relative);returnnewUrlInfo(url,Constants.UrlProviders.Media,culture);}}publicclassMyMediaUrlProviderComposer:IComposer{publicvoidCompose(IUmbracoBuilderbuilder)=>builder.MediaUrlProviders().Replace<DefaultMediaUrlProvider,MyMediaUrlProvider>();}
Target:origin/v17/dev · Based on commit:324eeaf1 · Skipped: 0 noise files
Fixes the double-? bug that occurred when a custom Media URL provider appended cache-busting query parameters and rich text rendering naively concatenated the stored query string onto the already-parameterised URL. The core change — swapping $"{mediaUrl}{queryString}" for mediaUrl.AppendQueryStringToUrl(queryString) — is exactly right for the happy path and is covered by new tests.
Other changes: Rich text rendering now always calls AppendQueryStringToUrl instead of string concatenation; behavior changes for images with cache-busting URLs (intended) and, inadvertently, for images without any query string (see Important findings below).
Important
src/Umbraco.Core/Templates/HtmlImageSourceParser.cs:139: When the stored image src has no query string (plain, uncropped image), src.Groups[2].Value is "". AppendQueryStringToUrl("") filters the empty string out of nonEmpty, then calls "".EnsureStartsWith('?') = "?" (or '&'), appending a stray trailing character to the URL — a regression vs. the old $"{mediaUrl}{""}" which cleanly produced mediaUrl. Guard before delegating: queryString.IsNullOrWhiteSpace() ? mediaUrl : mediaUrl.AppendQueryStringToUrl(queryString).
src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs:129: Same issue — currentImageQueryString is now string.Empty (previously null) when no ? is present. null was safe in the old string interpolation; "" is not safe as an argument to AppendQueryStringToUrl. Same guard pattern fixes both files.
Suggestions
tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlImageSourceParserTests.cs:281: The new tests cover the with-query-string path. Adding a test for a plain image (no crop params in src) + cache-busting provider would prevent the regression from going undetected. The suggested test is on the inline comment.
Request Changes
The fix correctly addresses the primary bug, but the empty-query-string edge case introduces a regression (trailing ? or & on plain image URLs) that should be fixed before merging.
kjac
changed the title
Add support for Media URL providers with cache busting
Media: Add support for Media URL providers with cache busting (Closes #23282)
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prerequisites
If there's an existing issue for this PR then this fixes #23282 (in part)
Description
Umbraco does not automatically append cache busting parameters to raw media URLs. This is by design, to prevent cache misses downstream.
This does however come with a few drawbacks, as described in ##23282.
One would then assume that a custom Media URL provider would be able to solve these drawbacks for a concrete implementation --- but unfortunately, the rich text rendering assumes a bit too much when rendering local image sources; it does not expect Media URLs with query string parameters.
In effect, if a Media URL provider yields Media URL with a cache buster parameter (e.g.
/media/something/image.png?rand=123), the rich text conversion ends up generating Media URLs with two?parts (/media/something/image.png?rand=123?rmode=max&....).This PR ensures that the rich text rendering appends query string parameters safely, thus enabling Media URL providers with cache busting by query string.
Note
Crop URLs are cache busted by default as-is, because the effective crops are expected to change over time.
Testing this PR
Replace the default Media URL provider with a custom implementation which adds cache busting (example code below), and verify that:
Verify all of the above both for templated rendering (example template below) and for the Delivery API output.
Example template
Example Media URL provider