---
title: Migrate the Conventions API from CMS 12 to CMS 13
slug: cms-13/docs/conventions-api-migration-guide
docTags: 
createdAt: 2026-07-30T20:22:02.544Z
---

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:**

```csharp
[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:**

```csharp
// In Startup.cs or Program.cs
services.AddContentGraph(configureConventions: conventions =>
{
    conventions.ForInstancesOf<ProductPage>()
        .IncludeField(p => p.TotalPrice());
});
```

You can also register conventions using `ConfigureGraphConventions`:

```csharp
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:**

```csharp
conventionRepo.ExcludeContentType<StandardPage>()
    .ExcludeContentType<OrderBlock>();
```

**CMS 13:**

```csharp
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:**

```csharp
conventionRepo.ExcludeAllContentTypes()
    .Except<StandardPage>()
    .Except<OrderBlock>();
```

**CMS 13:**

```csharp
// 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:**

```csharp
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:**

```csharp
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:**

```csharp
public class OrderBlock : BlockData
{
    public double TotalPrice() => Quantity * Price;
}

conventionRepo.ForInstancesOf<OrderBlock>()
    .IncludeField(p => p.TotalPrice());
```

**CMS 13 (recommended — extension methods):**

```csharp
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):**

```csharp
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:

```csharp
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:

```csharp
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:**

```csharp
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.

1. Define the contract as a C# interface.

```csharp
namespace MyProject.Models.Contracts
{
    [ContentType(DisplayName = "Categorizable", GUID = "CONTENT_TYPE_GUID")]
    public interface ICategorizable : IContentData
    {
        string Category { get; set; }

        string Tags { get; set; }
    }
}
```

2. Implement the contract in your content types.

```csharp
[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; }
}
```

3. Query using the contract in GraphQL.

```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**.

:::hint{type="info"}
**Note**
Contracts differ from the CMS 12 abstract or interface approach:

- Contracts define shared properties that all implementing content types must have, whereas `IncludeAbstract` and `IncludeInterface` only 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:**

```json
// In appsettings.json
{
  "Optimizely": {
    "ContentGraph": {
      "Include": {
        "ContentIds": [5, 10, 15],
        "ContentTypes": ["StandardPage", "ArticlePage"]
      },
      "OnlySyncContentTypesInWhitelistToSchema": true
    }
  }
}
```

```csharp
// Or in code
services.Configure<QueryOptions>(options =>
{
    options.Include.ContentTypes.Add("StandardPage");
    options.Exclude.Types.Add(typeof(InternalPage));
});
```

**CMS 13:**

In CMS 13, access rights control which content Optimizely Graph indexes. Optimizely Graph indexes only content that the **SearchIndexer** role can read. **SearchIndexer** is a virtual role that grants the permissions to index content. Controlling indexing through access rights gives CMS administrators control over each content item without code changes. It is more granular than the type-based configuration in CMS 12.

To exclude content from indexing, remove read access for the **SearchIndexer** role:

1. Go to **Settings** > **Set Access Rights**.
2. Select the content item you want to exclude from indexing.
3. Remove read access for the **SearchIndexer** role.

When you remove read access from content that Optimizely Graph already indexed, Optimizely Graph removes that content from the index. It also removes the descendants that inherit the content's access rights.

:::hint{type="warning"}
**Important**
CMS does not add the **SearchIndexer** role to custom access rights. If you clear **Inherit settings from parent item** on a content item, give the **SearchIndexer** role read access. Otherwise, Optimizely Graph does not index that content or the descendants that inherit its access rights. See [Verify SearchIndexer access rights](docId:5IIpWhRi9sd4hvXlqyaHY).
:::

## 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   |
