In a v17 project using headless setup, I want to implement at custom URL provider to replace hostname with frontend domain.
I reported an issue here, but it was closed:
opened 10:01AM - 01 Jun 26 UTC
closed 08:38AM - 07 Jul 26 UTC
state/needs-investigation
type/bug
area/backend
### Which Umbraco version are you using?
17.4.2
### Bug summary
In Umbraco 17… , I have implemented a custom URL provider (which is similar to what we did in a older v8 project):
```
public class ExternalUrlProvider : IUrlProvider
{
public string Alias => "externalUrlProvider";
public IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
=> Task.FromResult<UrlInfo?>(null);
public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content is null)
return null;
if (content is ExternalPage page)
{
var externalUrl = page.ExternalUrl;
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri) && externalUri is not null)
{
return UrlInfo.FromUri(externalUri, Alias, null);
}
}
return null;
}
}
```
It just shows default URL:
<img width="390" height="237" alt="Image" src="https://github.com/user-attachments/assets/256c2d39-e1e8-441c-bdca-889bfc421576" />
<img width="462" height="516" alt="Image" src="https://github.com/user-attachments/assets/a18ff40b-b848-420b-adc7-3384c5604725" />
I also tried adding this as part of extended from `NewDefaultUrlProvider`, but I don't see the external URL getting rendered in either back office URL shown in links box or in Delivery API output.
```
public class CustomUrlProvider : NewDefaultUrlProvider
{
private readonly FrontEndInfoOptions _frontEndInfoOption;
public CustomUrlProvider(
IOptionsMonitor<RequestHandlerSettings> requestSettings,
ILogger<NewDefaultUrlProvider> logger,
ISiteDomainMapper siteDomainMapper,
IUmbracoContextAccessor umbracoContextAccessor,
UriUtility uriUtility,
IPublishedContentCache publishedContentCache,
IDomainCache domainCache,
IIdKeyMap idKeyMap,
IDocumentUrlService documentUrlService,
IDocumentNavigationQueryService navigationQueryService,
IPublishedContentStatusFilteringService publishedContentStatusFilteringService,
ILanguageService languageService,
IOptions<FrontEndInfoOptions> frontEndInfoOption)
: base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, publishedContentCache,
domainCache, idKeyMap, documentUrlService, navigationQueryService, publishedContentStatusFilteringService, languageService)
{
_frontEndInfoOption = frontEndInfoOption.Value;
}
public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
if (defaultUrlInfo is null)
{
return null;
}
/*if (content is ExternalPage page)
{
var externalUrl = page.ExternalUrl;
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri))
{
return new UrlInfo(externalUri, defaultUrlInfo!.Provider, defaultUrlInfo!.Culture);
}
return null;
}*/
var baseUri = new Uri(_frontEndInfoOption.Domain!);
Uri? uri = defaultUrlInfo?.Url;
if (current == null || uri == null) return null;
if (!uri.IsAbsoluteUri)
{
uri = new Uri(baseUri, uri);
}
// Replace host and port.
var builder = new UriBuilder(uri)
{
Host = baseUri.Host,
Port = baseUri.Port
};
return new UrlInfo($"{builder.Uri.ToString().TrimEnd("/")}", defaultUrlInfo!.Provider, defaultUrlInfo!.Culture);
}
public override IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
}
```
### Specifics
_No response_
### Steps to reproduce
Implement a custom URL provider returning an external URL.
### Expected result / actual result
_No response_
It seems there were a similar issue here:
opened 03:06PM - 05 Mar 25 UTC
closed 10:05AM - 06 Mar 25 UTC
type/bug
release/15.3.0
### Which Umbraco version are you using? (Please write the *exact* version, exam… ple: 10.1.0)
15.2.1
### Bug summary
Bug summary
We are using a CustomUrlProvider to modify URLs by removing a specific part. The provider is implemented based on the documentation, and a simplified version looks like this:
```
public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content is null)
{
return null;
}
// Only apply this to news pages
if (content.ContentType.Alias == News.ModelTypeAlias)
{
// Get the original base url that the DefaultUrlProvider would have returned,
// it's important to call this via the base, rather than .Url, or UrlProvider.GetUrl to avoid cyclically calling this same provider in an infinite loop!!)
UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
if (defaultUrlInfo is null)
{
return null;
}
if (!defaultUrlInfo.IsUrl)
{
// This is a message (eg published but not visible because the parent is unpublished or similar)
return defaultUrlInfo;
}
else
{
// Manipulate the url somehow in a custom fashion:
var originalUrl = defaultUrlInfo.Text;
var repo = content.Ancestors().FirstOrDefault(f => f.ContentType.Alias == RepoFolder.ModelTypeAlias);
var customUrl = $"{originalUrl}";
if (repo != null)
{
customUrl = $"{originalUrl.Replace($"/{repo?.Name()}", "")}";
}
return new UrlInfo(customUrl, true, culture);
}
}
// Otherwise return null
return null;
}
```
This should transform the URL:
🔗 From: https://localhost:44302/publications/2025/test-1
🔗 To: https://localhost:44302/publications/test-1
When adding a link to a page (e.g., inside an RTE), the expected and stripped URL appears correctly in the frontend. However, in the Backoffice, under the Links section, the displayed URLs are incorrect:

### Observed Behavior
- The URLs displayed in the Links section of the Backoffice are incorrect and do not reflect the modifications made by CustomUrlProvider.
- Examining the network requests in the browser reveals a call to: /umbraco/management/api/v1/document/urls?id=da90354f-9dac-49ba-9bb7-48f9c454c4a7
- This endpoint does not trigger our CustomUrlProvider.
- As a result, the stripped URLs are not applied in the Backoffice.
Inside the network tab inside the browser, we can see following request to the URL /umbraco/management/api/v1/document/urls?id=da90354f-9dac-49ba-9bb7-48f9c454c4a7

Our Custom UrlProvider is never triggered, when this endpoint is called.
### Specifics
_No response_
### Steps to reproduce
- Implement the CustomUrlProvider as shown above.
- Create a content structure where the provider applies (e.g., a news page inside a repository folder).
- Add a link to the page inside an RTE.
- Observe that the frontend renders the correct stripped URL.
- Open the Backoffice and inspect the Links section for the node.
### Expected result / actual result
- The Links section in the Backoffice should display the modified URLs (with the stripped part removed).
- The API endpoint /umbraco/management/api/v1/document/urls should return the correct URLs, respecting the CustomUrlProvider.
I am somewhat unsure if the error is on our side or if there is someting wrong with the UrlProviders
Kenn suggested this, which also works as it is a relative URL:
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
namespace Umbraco.Cms.Web.UI.Custom;
public class MyUrlProvider : IUrlProvider
{
public string Alias => "MyUrlProvider";
public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) =>
new(new Uri($"/{content.Key}", UriKind.Relative), Alias, culture);
public IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
=> Task.FromResult<UrlInfo?>(null);
}
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Routing;
namespace Umbraco.Cms.Web.UI.Custom;
public class MyUrlProviderComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.UrlProviders().InsertBefore<NewDefaultUrlProvider, MyUrlProvider>();
}
However when I implement the following the route is not shown in back office links box although GetUrl returns the UrlInfo as expected.
public class ExternalUrlProvider : IUrlProvider
{
public string Alias => "ExternalUrlProvider";
public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content.ContentType.Alias != ExternalPage.ModelTypeAlias)
{
return null;
}
var externalUrl = content.Value<string>("externalUrl", culture);
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri) && externalUri is not null)
{
return UrlInfo.FromUri(externalUri, Alias, culture, true);
}
return null;
}
public IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
=> Task.FromResult<UrlInfo?>(null);
}
When I simple returns a relative URL it shows up:
public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content.ContentType.Alias != ExternalPage.ModelTypeAlias)
{
return null;
}
var externalUrl = content.Value<string>("externalUrl", culture);
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri) && externalUri is not null)
{
//return UrlInfo.FromUri(externalUri, Alias, culture, true);
return new(new Uri($"/{content.Key}", UriKind.Relative), Alias, culture);
}
return null;
}
In Delivery API the external URL is returned, but always starting with / in route although not a relative URL.
opened 10:01AM - 01 Jun 26 UTC
closed 08:38AM - 07 Jul 26 UTC
state/needs-investigation
type/bug
area/backend
### Which Umbraco version are you using?
17.4.2
### Bug summary
In Umbraco 17… , I have implemented a custom URL provider (which is similar to what we did in a older v8 project):
```
public class ExternalUrlProvider : IUrlProvider
{
public string Alias => "externalUrlProvider";
public IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
public Task<UrlInfo?> GetPreviewUrlAsync(IContent content, string? culture, string? segment)
=> Task.FromResult<UrlInfo?>(null);
public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content is null)
return null;
if (content is ExternalPage page)
{
var externalUrl = page.ExternalUrl;
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri) && externalUri is not null)
{
return UrlInfo.FromUri(externalUri, Alias, null);
}
}
return null;
}
}
```
It just shows default URL:
<img width="390" height="237" alt="Image" src="https://github.com/user-attachments/assets/256c2d39-e1e8-441c-bdca-889bfc421576" />
<img width="462" height="516" alt="Image" src="https://github.com/user-attachments/assets/a18ff40b-b848-420b-adc7-3384c5604725" />
I also tried adding this as part of extended from `NewDefaultUrlProvider`, but I don't see the external URL getting rendered in either back office URL shown in links box or in Delivery API output.
```
public class CustomUrlProvider : NewDefaultUrlProvider
{
private readonly FrontEndInfoOptions _frontEndInfoOption;
public CustomUrlProvider(
IOptionsMonitor<RequestHandlerSettings> requestSettings,
ILogger<NewDefaultUrlProvider> logger,
ISiteDomainMapper siteDomainMapper,
IUmbracoContextAccessor umbracoContextAccessor,
UriUtility uriUtility,
IPublishedContentCache publishedContentCache,
IDomainCache domainCache,
IIdKeyMap idKeyMap,
IDocumentUrlService documentUrlService,
IDocumentNavigationQueryService navigationQueryService,
IPublishedContentStatusFilteringService publishedContentStatusFilteringService,
ILanguageService languageService,
IOptions<FrontEndInfoOptions> frontEndInfoOption)
: base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, publishedContentCache,
domainCache, idKeyMap, documentUrlService, navigationQueryService, publishedContentStatusFilteringService, languageService)
{
_frontEndInfoOption = frontEndInfoOption.Value;
}
public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
if (defaultUrlInfo is null)
{
return null;
}
/*if (content is ExternalPage page)
{
var externalUrl = page.ExternalUrl;
if (!string.IsNullOrWhiteSpace(externalUrl) && Uri.TryCreate(externalUrl, UriKind.Absolute, out var externalUri))
{
return new UrlInfo(externalUri, defaultUrlInfo!.Provider, defaultUrlInfo!.Culture);
}
return null;
}*/
var baseUri = new Uri(_frontEndInfoOption.Domain!);
Uri? uri = defaultUrlInfo?.Url;
if (current == null || uri == null) return null;
if (!uri.IsAbsoluteUri)
{
uri = new Uri(baseUri, uri);
}
// Replace host and port.
var builder = new UriBuilder(uri)
{
Host = baseUri.Host,
Port = baseUri.Port
};
return new UrlInfo($"{builder.Uri.ToString().TrimEnd("/")}", defaultUrlInfo!.Provider, defaultUrlInfo!.Culture);
}
public override IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
=> [];
}
```
### Specifics
_No response_
### Steps to reproduce
Implement a custom URL provider returning an external URL.
### Expected result / actual result
_No response_