While working with the Content Delivery API in Umbraco 17, I recently needed to add some extra content to every API response.
The content was not a property of the requested page itself. It lived somewhere else in the content tree. Think about a featured article configured on the site root, global site settings, navigation data, or an error page.
Of course, I could create a custom endpoint and build my own response model. But in this case I wanted to keep using the normal Umbraco Delivery API endpoints and simply extend the response.
After looking through the Umbraco source and API documentation, I found that this can be done by replacing the default IApiContentResponseBuilder.
The useful part is that we can also use IApiContentBuilder to convert an IPublishedContent item into the same structure Umbraco already uses inside the Delivery API.
By default, a Delivery API response contains the requested content item and its properties.
{
"name": "Home",
"createDate": "2026-08-20T10:00:00Z",
"updateDate": "2026-08-28T08:30:00Z",
"route": {
"path": "/"
},
"id": "00000000-0000-0000-0000-000000000000",
"contentType": "home",
"properties": {
"title": "Welcome"
}
}
But what if I want to add something like this?
{
"name": "Home",
"contentType": "home",
"properties": {
"title": "Welcome"
},
"featuredArticle": {
"name": "My featured article",
"contentType": "article",
"properties": {
"title": "My featured article"
}
}
}
The featuredArticle does not belong to the Home document type. It is resolved separately and added to the API response.
Umbraco builds the response for a content item through IApiContentResponseBuilder.
The default implementation is ApiContentResponseBuilder, and in Umbraco 17 the Create method is protected and can be overridden.
That gives us a nice place to extend the normal response without rebuilding the complete Delivery API ourselves.
public class CustomApiContentResponseBuilder : ApiContentResponseBuilder
{
private readonly IApiContentBuilder _apiContentBuilder;
public CustomApiContentResponseBuilder(
IApiContentNameProvider apiContentNameProvider,
IApiContentRouteBuilder apiContentRouteBuilder,
IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor,
IVariationContextAccessor variationContextAccessor,
IApiContentBuilder apiContentBuilder)
: base(
apiContentNameProvider,
apiContentRouteBuilder,
outputExpansionStrategyAccessor,
variationContextAccessor)
{
_apiContentBuilder = apiContentBuilder;
}
protected override IApiContentResponse Create(
IPublishedContent content,
string name,
IApiContentRoute route,
IDictionary<string, object?> properties)
{
var featuredArticle = GetFeaturedArticle(content);
return new CustomApiContentResponse(
content.Key,
name,
content.ContentType.Alias,
content.CreateDate,
content.UpdateDate,
route,
properties,
GetCultures(content),
featuredArticle);
}
private IApiContent? GetFeaturedArticle(IPublishedContent content)
{
// this is dummy, here you could find the article from the current content. For Example use Root and then find latest Article with feature flag.
return apiContentBuilder.Build(content)
}
}
The important part here is not only that we can override Create, but also the use of IApiContentBuilder.
My first thought was to create a DTO for the featured article and manually map all the properties I needed.
That works, but it also means duplicating part of the logic Umbraco already has.
IApiContentBuilder can take an IPublishedContent instance and build an IApiContent object from it:
IApiContent? apiContent = _apiContentBuilder.Build(article);This is useful because the content goes through the same Delivery API building process that Umbraco already uses.
So instead of doing something like:
return new FeaturedArticleDto
{
Id = article.Key,
Name = article.Name,
Title = article.Value<string>("title"),
Image = ...
};
I can let Umbraco build the content representation for me.
This is especially useful when the selected content contains properties that already have Delivery API converters.
Umbraco itself also injects IApiContentBuilder into several internal components, such as content picker conversion and webhook payload generation, so it is not just an interface that happens to exist without being used.
The next step is to create a response type with the extra property
.
public class CustomApiContentResponse : ApiContentResponse
{
public CustomApiContentResponse(
Guid id,
string name,
string contentType,
DateTime createDate,
DateTime updateDate,
IApiContentRoute route,
IDictionary<string, object?> properties,
IDictionary<string, IApiContentRoute> cultures,
IApiContent? featuredArticle)
: base(
id,
name,
contentType,
createDate,
updateDate,
route,
properties,
cultures)
{
FeaturedArticle = featuredArticle;
}
public IApiContent? FeaturedArticle { get; }
}
Now the normal Delivery API response can contain the additional featuredArticle field.
Finally, replace Umbraco's default IApiContentResponseBuilder registration with the custom implementation.
I use a composer for this:
public class DeliveryApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddUnique<
IApiContentResponseBuilder,
CustomApiContentResponseBuilder>();
}
}
From that point, the custom builder is used when Umbraco builds the normal content response.
You do not need to create a completely separate endpoint just to add the extra field.
The main reason I like this approach is that the extra content still looks like normal Delivery API content.
I do not have to maintain another mapping layer for things such as:
content type
name
route
property conversion
nested Delivery API values
When Umbraco already knows how to convert the content, I would rather reuse that logic.
It also means the response stays more consistent for the frontend consuming it.
Other use cases
A featured article is only one example.
The same approach can be useful for:
global website settings
navigation configuration
footer content
a globally configured error page
related content
breadcrumbs
calculated or resolved content that does not belong on the requested document type
I would not use this for everything. If the data really belongs to a specific document type, I still prefer modelling it there.
But for global or calculated information that needs to travel with a Delivery API response, this is a useful extension point.
IApiContentBuilder.Build() does real work.
Calling it once for a settings node or featured article is normally not something I would worry about. Calling it repeatedly in a large loop is different.
For example, I would avoid doing this without first checking the impact:
foreach (var item in hundredsOfItems)
{
var apiItem = _apiContentBuilder.Build(item);
}
Especially with more complex properties and expansion, that can add unnecessary work to every API request.
Keep the extra response data small and intentional.
The Delivery API already contains quite a bit of useful infrastructure that is easy to miss when you only look at the documented endpoints.
In this case, the combination of ApiContentResponseBuilder and IApiContentBuilder allowed me to keep the standard Umbraco response while still adding content from somewhere else in the tree.
For me, that is preferable to creating custom JSON models and manually recreating logic that Umbraco already has.
The API documentation for ApiContentResponseBuilder and IApiContentBuilder is worth keeping nearby when you need to customize the Delivery API beyond normal property conversion.