Upgrade Content Delivery API from version 3 to version 13
Describes how to upgrade Content Delivery API from version 3 to version 13, including the System.Text.Json migration, the new base endpoint, and the removal of Content Definitions API and Content Management API.
Content Delivery API 13 runs on Optimizely CMS 13 and .NET 10, and serializes content with System.Text.Json instead of Newtonsoft.Json. Upgrade to keep delivering content through a supported API after you move a solution off CMS 12. This article covers the package updates, the serializer changes, and the endpoint changes that your client applications require.
Version 13 is the supported path for solutions that run Content Delivery API 3.x. Upgrade to version 13 rather than rebuilding the integration on another API.
Version 13 also removes Content Definitions API and Content Management API. The version number moves from 3 to 13 so that it matches the CMS release it supports.
ImportantThe base endpoint moves from
/api/episerver/v3.0to/api/episerver/v4.0. Every client application breaks until you update its API URLs. Plan a coordinated release of the solution and its client applications.
For details about the CMS platform changes behind this upgrade, see Upgrade to CMS 13 from CMS 12.
Prerequisites
Before you start the upgrade, verify the following requirements:
- Your solution runs Optimizely CMS 13.0.2 or a later 13.x release.
- Your project targets .NET 10.
- Your solution runs Commerce Connect 15.0.0 or a later 15.x release, when the solution uses Commerce.
- Your solution runs Optimizely Forms 6.0.1 or a later 6.x release, when the solution uses Forms.
- You hold access to update both the server-side project files and the client applications that call the API.
- You hold a staging environment for testing the upgrade before production.
- You hold an inventory of custom
IPropertyModel,ContentApiModelFilter, andIJsonConverterimplementations in the solution. - You hold an inventory of the calls your solution makes to Content Management API and Content Definitions API. Version 13 adds no replacement for either, so those calls need a separate integration. See Content Definitions API and Content Management API.
Upgrade sequence
Complete the upgrade in the following order, because each step depends on the one before it:
- Upgrade CMS to version 13 and retarget the project to .NET 10.
- Update the Content Delivery API package references.
- Replace the Newtonsoft.Json serializer configuration with System.Text.Json.
- Remove the references to Content Definitions API and Content Management API.
- Update the client applications to the new base endpoint and response model.
- Build the solution and resolve the remaining errors.
- Test the API responses against the client applications in a staging environment.
Server-side changes
The changes in this section apply to the C# solution that hosts the API.
Update NuGet packages
Update the package references in your project file to version 13.0.0:
<PackageReference Include="EPiServer.ContentDeliveryApi.Core" Version="13.0.0" />
<PackageReference Include="EPiServer.ContentDeliveryApi.Cms" Version="13.0.0" />Solutions that use Commerce Connect also update the Commerce package:
<PackageReference Include="EPiServer.ContentDeliveryApi.Commerce" Version="13.0.0" />Remove Newtonsoft.Json dependencies
Content Delivery API 13.0 uses System.Text.Json. Remove the Newtonsoft.Json packages that served the Content API alone:
<!-- Remove when only the Content API required this package -->
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="..." />
NoteKeep Newtonsoft.Json installed when other parts of your solution depend on it. The Content Delivery API packages no longer reference it.
For the framework-wide Newtonsoft.Json removals in CMS 13, see Framework and platform breaking changes.
Update JSON attribute usage
Update the model classes that carry Newtonsoft.Json attributes and feed the Content API. The following table maps each attribute to its System.Text.Json equivalent:
| Newtonsoft.Json | System.Text.Json |
|---|---|
using Newtonsoft.Json; | using System.Text.Json.Serialization; |
[JsonProperty("name")] | [JsonPropertyName("name")] |
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |
[JsonIgnore] | [JsonIgnore] (namespace change only) |
[JsonConverter(typeof(X))] | [JsonConverter(typeof(X))] (namespace change only) |
[JsonExtensionData] | [JsonExtensionData] (namespace change only) |
Update custom JSON converters
Custom JSON converters registered through IJsonConverter now extend System.Text.Json.Serialization.JsonConverter<T> rather than Newtonsoft.Json.JsonConverter. Version 3.x used the following pattern:
public class MyConverter : Newtonsoft.Json.JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { ... }
public override object ReadJson(JsonReader reader, Type type, object existing, JsonSerializer serializer) { ... }
public override bool CanConvert(Type objectType) => objectType == typeof(MyModel);
}Version 13.0 uses the following pattern:
public class MyConverter : System.Text.Json.Serialization.JsonConverter<MyModel>
{
public override void Write(Utf8JsonWriter writer, MyModel value, JsonSerializerOptions options) { ... }
public override MyModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { ... }
}Update serializer configuration
The serializer configuration callback receives JsonSerializerOptions from System.Text.Json rather than JsonSerializerSettings from Newtonsoft.Json. Version 3.x used the following configuration:
services.ConfigureContentDeliveryApiSerializer(settings =>
{
settings.NullValueHandling = NullValueHandling.Ignore;
settings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
};
});Version 13.0 uses the following configuration:
services.ConfigureContentDeliveryApiSerializer(options =>
{
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
});The following table maps each Newtonsoft.Json setting to its System.Text.Json equivalent:
| Newtonsoft.Json | System.Text.Json |
|---|---|
NullValueHandling.Ignore | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull |
CamelCaseNamingStrategy | PropertyNamingPolicy = JsonNamingPolicy.CamelCase |
StringEnumConverter | JsonStringEnumConverter(JsonNamingPolicy.CamelCase) |
MaxDepth = 32 | MaxDepth = 32 |
MissingMemberHandling.Ignore | Default behavior, so no action follows |
TypeNameHandling.None | Default behavior, so no action follows |
Solutions that use Commerce Connect apply the same changes to ConfigureCommerceApiSerializer.
Set camelCase keys on custom property models
System.Text.Json does not apply DictionaryKeyPolicy to [JsonExtensionData] dictionary keys. This affects custom IPropertyModel implementations and custom ContentApiModelFilter implementations that add keys to ContentApiModel.Properties.
In version 3.x, the Newtonsoft.Json CamelCasePropertyNamesContractResolver converted every dictionary key to camelCase. [JsonExtensionData] keys received the same treatment. Version 3.x accepted a PascalCase key:
contentApiModel.Properties.Add("PreviewUrl", value); // Serialized as "previewUrl"In version 13.0, set the key in camelCase yourself:
contentApiModel.Properties.Add("previewUrl", value); // Serialized as writtenA custom property key sometimes collides with a built-in ContentApiModel property name after camelCase conversion. A content property named Url becomes url, which matches the built-in url property. Content Delivery API skips the colliding property to avoid duplicate JSON keys.
Replace ConfigureForExternalTemplates
ConfigureForExternalTemplatesVersion 13.0 deprecates ConfigureForExternalTemplates(). Register headless applications in CMS 13 as RemoteWebsite instead. A RemoteWebsite is an application type for an external front end. It holds the front-end host and edit URLs, and serves no templates from CMS.
Version 3.x used the following registration:
services.AddContentApiCore();
services.ConfigureForExternalTemplates();In version 13.0, configure the application as a RemoteWebsite in the CMS 13 admin interface, then remove the ConfigureForExternalTemplates() call. To disable template validation for content URLs, set the option directly:
services.AddContentApiCore(options =>
{
options.ValidateTemplateForContentUrl = false;
});Update the renamed site types
CMS 13 replaces the SiteDefinition model with the Application model, and Content Delivery API follows that change. Version 13.0 handles the renamed types in two ways. It removes some outright, and marks others obsolete. The following table lists each type and the result at compile time:
| Version 3.x | Version 13.0 | Compile result |
|---|---|---|
SiteDefinitionModel | ApplicationModel | Error, because version 13.0 removes the type |
ContentApiTrackingContext.ReferencedSites | ContentApiTrackingContext.ReferencedApplications | Error, because version 13.0 removes the property |
SiteDefinitionLanguageModel | ApplicationLanguageModel | Warning, because the type stays as obsolete |
DependencyTypes.Site | DependencyTypes.Application | Warning, because the constant stays as obsolete |
ReferencedSiteMetadata | ReferencedApplicationMetadata | Warning, because the type stays as obsolete |
Update every entry in the table, including the obsolete ones. A later release removes the obsolete types.
The ReferencedApplicationMetadata constructor also takes a different first argument. ReferencedSiteMetadata took a globally unique identifier (GUID), and ReferencedApplicationMetadata takes the application name as a string.
For the wider CMS 13 changes behind the Application model, see Sites-to-applications and routing breaking changes.
Remove Search & Navigation package references
Version 13.0 drops the Content Search API packages. Remove the following package references:
<!-- Remove -->
<PackageReference Include="EPiServer.ContentDeliveryApi.Search" Version="..." />
<PackageReference Include="EPiServer.ContentDeliveryApi.Search.Commerce" Version="..." />Client application changes
The changes in this section apply to the applications that consume the API. Review each one against the client code before you deploy.
Base endpoint change
The base endpoint moves from /api/episerver/v3.0 to /api/episerver/v4.0. Update every API URL in your client applications.
Site endpoint changes
The endpoint path /site/ and the response header x-epi-siteid keep their version 3.x names. The underlying CMS model moves from SiteDefinition to Application, which changes the endpoint in three ways.
First, the single-item endpoint accepts a name instead of an identifier. Version 13.0 accepts a site name string at GET /api/episerver/v4.0/site/{name}. Version 3.x accepted a globally unique identifier (GUID) at GET /api/episerver/v3.0/site/{id}. A request that carries a name with no match returns 404 Not Found rather than 400 Bad Request.
Second, version 13.0 removes the id property from the response. Use name to identify applications. The response takes the following shape:
{
"name": "My Site",
"editLocation": "...",
"contentRoots": { ... },
"languages": [ ... ],
"hosts": [ ... ]
}Third, the x-epi-siteid header carries the application name as a string rather than a GUID.
Two further behaviors change in the response:
- Content roots – CMS 13 adds a
blueprintscontent root. A blueprint is a reusable content structure in CMS 13, and theblueprintsroot holds those structures. The response always includes this root incontentRoots, whatever the value of theIncludeInternalContentRootssetting. Handle the extra entry in code that iterates the dictionary. - No wildcard hosts – The response returns only the hosts that you configure explicitly. CMS 13 rejects a wildcard host. The validation error reads
Host cannot be a wildcard '*'. Make the application default instead.Mark the application as default, then update client code that tests for a wildcard host.
JSON serialization differences
System.Text.Json output differs from Newtonsoft.Json output in the following cases. Review each one against the parsing logic in your client applications.
- Null handling – By default, the response includes properties that hold
null. SetDefaultIgnoreConditiontoJsonIgnoreCondition.WhenWritingNullto omit them. - Number handling – System.Text.Json applies stricter rules to number types. A value such as
1.0serializes as1for an integer type. - String-to-number coercion – System.Text.Json rejects a JSON string where a numeric type belongs. Newtonsoft.Json converted the JSON string
"99"to the integer99. System.Text.Json throws aJsonExceptionfor the same value.
Custom date formats also need review, because System.Text.Json and Newtonsoft.Json apply them differently. Both libraries write ISO 8601 values by default, such as 2024-01-15T10:30:00Z.
Removed APIs
Version 13.0 removes two of the three APIs that shipped in version 3.x. Content Delivery API is the only one that remains.
Content Definitions API and Content Management API
Content Delivery API 13.0 ships neither EPiServer.ContentDefinitionsApi, EPiServer.ContentDefinitionsApi.Commerce, nor EPiServer.ContentManagementApi. Remove the package references:
<!-- Remove -->
<PackageReference Include="EPiServer.ContentDefinitionsApi" Version="..." />
<PackageReference Include="EPiServer.ContentDefinitionsApi.Commerce" Version="..." />
<PackageReference Include="EPiServer.ContentManagementApi" Version="..." />Remove the matching service registrations from Startup.cs:
// Remove from ConfigureServices
services.AddContentDefinitionsApi(...);
services.AddContentManagementApi(...);Version 13 adds no replacement for either API. Optimizely has no plans to upgrade them, so no version 13 equivalent of EPiServer.ContentManagementApi or EPiServer.ContentDefinitionsApi follows.
CMS 13 exposes content management through the CMS REST API at /_cms/v1, which AddCms() registers automatically. Treat that API as a separate integration, not a drop-in replacement. Account for two differences before you plan the work:
- Authentication – The CMS REST API supports the client credentials grant only. Content Management API also accepted the authorization code and resource owner password grants. Solutions that used either one need a new authentication design.
- API surface – The endpoints, routes, and payloads differ from Content Management API and Content Definitions API. Budget for rewriting the calls rather than repointing them.
For the endpoint reference and the authentication setup, see Introduction to the CMS REST API.
OpenID Connect scopes for the removed APIs
Remove the scopes for the removed APIs from your OpenID Connect configuration:
// Remove these scopes from OpenIDConnectApplication configurations
ContentDefinitionsApiOptionsDefaults.Scope // "epi_content_definitions"
ContentManagementApiOptionsDefaults.Scope // "epi_content_management"OpenID Connect changes
Update OpenID Connect packages
Update the OpenID Connect package references to version 13.0.0:
<PackageReference Include="EPiServer.OpenIDConnect" Version="13.0.0" />
<PackageReference Include="EPiServer.OpenIDConnect.UI" Version="13.0.0" />Protected module path change
CMS 13 moves the protected module root, the path that serves the Optimizely user interfaces, from /EPiServer/ to /Optimizely/. CMS 13 serves the OpenID Connect interface from /Optimizely/OpenIDConnect/ instead of /EPiServer/OpenIDConnect/. Update any application that references the path directly.
The same change applies to the CMS edit interface, which moves from /EPiServer/CMS/Content/ to /Optimizely/CMS/Content/.
Package version compatibility
Version 13.0 declares a supported range for each Optimizely dependency. The following table maps the versions across the two releases:
| Package | Version 3.x | Version 13.0 |
|---|---|---|
EPiServer.ContentDeliveryApi.Core | 3.x | 13.0.0 |
EPiServer.ContentDeliveryApi.Cms | 3.x | 13.0.0 |
EPiServer.ContentDeliveryApi.Commerce | 3.x | 13.0.0 |
EPiServer.Cms | 12.x | 13.0.2 up to, but excluding, 14.0 |
EPiServer.Commerce.Core | 14.x | 15.0.0 up to, but excluding, 16.0 |
EPiServer.Forms | 5.x | 6.0.1 up to, but excluding, 7.0 |
| .NET | 6.0 | 10.0 |
| JSON serializer | Newtonsoft.Json | System.Text.Json |
The Commerce package carries its own version number, 15.0.0. The other Content Delivery API packages use 13.0.0.
Troubleshooting
Build error: JsonSerializerSettings not found
JsonSerializerSettings not foundYour code references the version 3.x serializer configuration type. Replace JsonSerializerSettings with JsonSerializerOptions, and change the using directive from Newtonsoft.Json to System.Text.Json. See Update serializer configuration.
Build error: JObject or JToken not found
JObject or JToken not foundYour code references the Newtonsoft.Json dynamic JSON types. Replace them with the System.Text.Json equivalents in the following table:
| Newtonsoft.Json | System.Text.Json |
|---|---|
JObject | JsonDocument, JsonElement, or JsonNode |
JToken | JsonElement or JsonNode |
JArray | JsonElement.EnumerateArray() or JsonArray |
JObject.Parse(json) | JsonDocument.Parse(json).RootElement |
JToken.FromObject(obj) | JsonSerializer.SerializeToElement(obj) |
Build error: SiteDefinitionModel not found
SiteDefinitionModel not foundVersion 13.0 removes SiteDefinitionModel. Replace it with ApplicationModel. The related type SiteDefinitionLanguageModel stays as obsolete, so it raises a warning rather than this error. See Update the renamed site types.
Runtime error: JsonSerializerOptions must specify a TypeInfoResolver
JsonSerializerOptions must specify a TypeInfoResolverYour code creates JsonSerializerOptions manually and passes them to an ASP.NET Core formatter. Set the resolver on the options:
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();Runtime error: No service for type 'ServiceAccessor<Application>'
No service for type 'ServiceAccessor<Application>'CMS 13 does not register ServiceAccessor<Application> in the dependency injection container. Inside an HTTP request context, resolve the application through IApplicationResolver:
var resolver = serviceProvider.GetRequiredService<IApplicationResolver>();
var currentApp = resolver.GetByContext();Outside an HTTP request context, such as a background task, resolve it through IApplicationRepository:
var repository = serviceProvider.GetRequiredService<IApplicationRepository>();
var app = repository.List().First();Both interfaces also expose asynchronous members, GetByContextAsync and ListAsync. Prefer the asynchronous overloads in request handlers, which is the pattern that the Alloy sample templates follow.
Content properties missing from the JSON response
The JSON response omits custom content properties, or returns them with PascalCase keys. System.Text.Json does not apply DictionaryKeyPolicy to [JsonExtensionData] dictionary keys. A property stored under a PascalCase key such as MainContentArea serializes as MainContentArea rather than mainContentArea. See Set camelCase keys on custom property models.
Updated about 6 hours ago