Migrate the Conventions API from CMS 12 to CMS 13
Migrate your Optimizely Graph Conventions API configuration from CMS 12 to CMS 13 with feature-by-feature mapping and code examples.
The Optimizely Graph Conventions API lets developers customize how content types and fields are indexed in Optimizely Graph. This article maps each CMS 12 Conventions API feature to its CMS 13 equivalent and provides code examples for each migration step.
Registration changes
In CMS 12, conventions are configured in an IInitializableModule using the ConventionRepository singleton. In CMS 13, conventions are configured during service registration in Startup.cs using the configureConventions parameter of AddContentGraph.
CMS 12:
[ModuleDependency(typeof(InitializationModule))]
public class GraphConventions : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var conventionRepo = context.Locate.Advanced
.GetInstance<ConventionRepository>();
conventionRepo.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice());
}
public void Uninitialize(InitializationEngine context) { }
}CMS 13:
// In Startup.cs or Program.cs
services.AddContentGraph(configureConventions: conventions =>
{
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice());
});You can also register conventions using ConfigureGraphConventions:
services.ConfigureGraphConventions(conventions =>
{
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice());
});Feature-by-feature migration
Exclude content types
The ExcludeContentType<T>() method uses the same syntax in CMS 13.
CMS 12:
conventionRepo.ExcludeContentType<StandardPage>()
.ExcludeContentType<OrderBlock>();CMS 13:
conventions.ExcludeContentType<StandardPage>()
.ExcludeContentType<OrderBlock>();Exclude all content types except some
CMS 12 provides ExcludeAllContentTypes().Except<T>() to exclude all content types except specified ones. This method is not available in CMS 13. Instead, use multiple ExcludeContentType<T>() calls to exclude specific types individually.
CMS 12:
conventionRepo.ExcludeAllContentTypes()
.Except<StandardPage>()
.Except<OrderBlock>();CMS 13:
// Exclude types individually instead
conventions.ExcludeContentType<BlogPage>()
.ExcludeContentType<InternalPage>()
.ExcludeContentType<DraftBlock>();Exclude fields
CMS 12 provides ExcludeField() to exclude specific properties from a content type. This method is not available in CMS 13. Instead, use the Admin UI to disable individual properties.
CMS 12:
conventionRepo.ForInstancesOf<StandardPage>()
.ExcludeField(p => p.ContentAreaItem1)
.ExcludeField(p => p.MainBody);CMS 13:
Go to Settings > Content Types in the Admin UI. Select the content type (such as StandardPage), find the property you want to exclude, and set its Property Indexing Type to Disabled.
Set indexing type for fields
CMS 12 provides Set() to override the indexing type for existing CMS properties. This method is not available in CMS 13. Instead, use the Admin UI.
CMS 12:
conventionRepo.ForInstancesOf<StandardPage>()
.Set(page => page.Keywords, IndexingType.Searchable)
.Set(page => page.Price, IndexingType.Queryable)
.Set(page => page.Quantity, IndexingType.OnlyStored);CMS 13:
Go to Settings > Content Types in the Admin UI. Select the content type, find the property, and set its Property Indexing Type to the desired value.
Include computed fields
The IncludeField() method is available in CMS 13 with enhancements. The basic syntax remains the same. CMS 13 adds support for async methods, service injection, extension methods, static methods, and Plain Old CLR Object (POCO) return types.
In CMS 13, define computed fields as extension methods to keep indexing logic separate from the content model.
CMS 12:
public class OrderBlock : BlockData
{
public double TotalPrice() => Quantity * Price;
}
conventionRepo.ForInstancesOf<OrderBlock>()
.IncludeField(p => p.TotalPrice());CMS 13 (recommended — extension methods):
public static class OrderBlockComputedFields
{
public static double TotalPrice(this OrderBlock block)
=> block.Quantity * block.Price;
}
conventions.ForInstancesOf<OrderBlock>()
.IncludeField(p => p.TotalPrice());CMS 13 (with indexing type):
conventions.ForInstancesOf<OrderBlock>()
.IncludeField(p => p.TotalPrice(), IndexingType.Queryable);Type inheritance
In CMS 12 and CMS 13, computed fields configured on a base type automatically appear on all derived content types. No additional configuration is needed for derived types.
New capabilities in CMS 13
CMS 13 introduces several capabilities for computed fields that are not available in CMS 12.
Async methods — For I/O-bound operations:
public static class ProductPageComputedFields
{
public static Task<string> ComputedSlugAsync(
this ProductPage page,
CancellationToken cancellationToken = default)
=> Task.FromResult(page.Name?.ToLowerInvariant().Replace(' ', '-') ?? "untitled");
}
// Registration
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedSlugAsync());Service injection — Access application services during indexing:
public static class ProductPageComputedFields
{
public static int ComputedChildCount(this ProductPage page, IServiceProvider sp = null!)
{
var loader = sp.GetRequiredService<IContentLoader>();
return loader.GetChildren<IContent>(page.ContentLink).Count();
}
}
// Registration
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedChildCount());Include abstract types and interface types in GraphQL
CMS 12 provides IncludeAbstract<T>() and IncludeInterface<T>() to include abstract classes and interfaces in the GraphQL schema. These methods are not available in CMS 13. Instead, use the Contracts feature, which provides a more integrated approach.
CMS 12:
conventionRepo
.IncludeAbstract<MyAbstractPage>()
.IncludeAbstract<MyAbstractPage2>()
.IncludeInterface<ISearchPage>();CMS 13 — using Contracts:
Contracts (also known as interfaces) define shared properties across content types. They automatically generate a corresponding GraphQL schema in Optimizely Graph.
- Define the contract as a C# interface.
namespace MyProject.Models.Contracts
{
[ContentType(DisplayName = "Categorizable", GUID = "CONTENT_TYPE_GUID")]
public interface ICategorizable : IContentData
{
string Category { get; set; }
string Tags { get; set; }
}
}- Implement the contract in your content types.
[ContentType(DisplayName = "Blog Article Page", GUID = "CONTENT_TYPE_GUID")]
public class BlogArticlePage : PageData, ICategorizable
{
[Display(
Name = "Category",
GroupName = SystemTabNames.Content,
Order = 10)]
[CultureSpecific]
public virtual string Category { get; set; }
[Display(
Name = "Tags",
GroupName = SystemTabNames.Content,
Order = 20)]
[CultureSpecific]
public virtual string Tags { get; set; }
}- Query using the contract in GraphQL.
query {
Categorizable(where: { Category: { eq: "Meetup" } }) {
items {
Category
Tags
... on BlogArticlePage {
Heading
MainBody
}
}
}
}Create contracts through the Admin UI by going to Settings > Content Types and selecting Create New > Contract.
NoteContracts differ from the CMS 12 abstract or interface approach:
- Contracts define shared properties that all implementing content types must have, whereas
IncludeAbstractandIncludeInterfaceonly expose existing class hierarchies to GraphQL.- Contracts are first-class CMS entities visible in the Admin UI, not just code-level constructs.
- Contract schemas are automatically generated in Optimizely Graph, enabling unified querying across all implementing content types.
Include and exclude content from indexing
CMS 12 provides QueryOptions.Include and QueryOptions.Exclude to control which content is indexed to Optimizely Graph. Include specific content IDs or content type names, and exclude specific .NET types from synchronization.
CMS 12:
// In appsettings.json
{
"Optimizely": {
"ContentGraph": {
"Include": {
"ContentIds": [5, 10, 15],
"ContentTypes": ["StandardPage", "ArticlePage"]
},
"OnlySyncContentTypesInWhitelistToSchema": true
}
}
}// Or in code
services.Configure<QueryOptions>(options =>
{
options.Include.ContentTypes.Add("StandardPage");
options.Exclude.Types.Add(typeof(InternalPage));
});CMS 13:
Optimizely Graph indexes all content by default in CMS 13. To exclude content from indexing, remove read permissions from the SearchIndexer user group for the content you want to exclude.
- Go to Settings > Set Access Rights in the CMS Admin UI.
- Go to the content tree node you want to exclude from indexing.
- Remove read access for the SearchIndexer group.
Content the SearchIndexer group cannot read is not indexed to Optimizely Graph. This approach gives CMS administrators per-content-node control without code changes. It is more granular than the type-based configuration in CMS 12.
Quick reference
| CMS 12 feature | CMS 13 equivalent |
|---|---|
ConventionRepository in IInitializableModule | configureConventions in AddContentGraph |
ExcludeContentType<T>() | ExcludeContentType<T>() (same API) |
ExcludeAllContentTypes().Except<T>() | Multiple ExcludeContentType<T>() calls |
ForInstancesOf<T>().ExcludeField(...) | Admin UI: set Property Indexing Type to Disabled |
ForInstancesOf<T>().Set(..., IndexingType) | Admin UI: set Property Indexing Type |
ForInstancesOf<T>().IncludeField(...) | ForInstancesOf<T>().IncludeField(...) (enhanced) |
IncludeAbstract<T>() | Contracts feature |
IncludeInterface<T>() | Contracts feature |
QueryOptions.Include / QueryOptions.Exclude | Remove read permissions from SearchIndexer group |
Updated about 1 hour ago
