Indexing conventions
Configure indexing conventions to customize how content types are indexed to Optimizely Graph, including computed fields, type exclusions, and indexing types.
Configure indexing conventions to customize how content types are indexed to Optimizely Graph. Use the configureConventions parameter in AddContentGraph during application startup.
services.AddContentGraph(configureConventions: conventions =>
{
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice())
.IncludeField(p => p.ComputedTitle(), IndexingType.Searchable);
conventions.ExcludeContentType<InternalPage>();
});
NoteContent types and content data are not resynced automatically when conventions change and the site restarts. After changing conventions in code and redeploying the site, re-run the Optimizely Graph Full Synchronization job so data is consistent with your configurations. Not doing so results in unpredictable outcomes.
Exclude content types
Exclude content types from syncing to the Optimizely Graph schema. Optimizely Graph does not index content of excluded content types.
The following example excludes InternalPage and DraftBlock:
conventions.ExcludeContentType<InternalPage>()
.ExcludeContentType<DraftBlock>();When excluded, InternalPage and DraftBlock are removed from the GraphQL schema, and their content is not synced to Optimizely Graph.
When you add content of excluded content types to a ContentArea or ContentAreaItem property of another type, those items are not included in the indexed content area.
Computed fields
Indexing conventions let you add computed fields to content types at indexing time without modifying the content model itself. Computed fields are methods whose return values are indexed alongside regular CMS properties.
NoteDefine computed fields as extension methods rather than instance methods on the content type. This keeps the content model clean and separates indexing logic from the domain model. Extension methods are the recommended approach for computed fields.
Define computed fields
Define computed fields as extension methods in a separate static class. This keeps indexing logic separate from the content model.
The following example uses a page type called ProductPage:
[ContentType]
public class ProductPage : PageData
{
public virtual string ProductName { get; set; }
public virtual int Quantity { get; set; }
public virtual double Price { get; set; }
}Define the computed field as an extension method:
public static class ProductPageComputedFields
{
public static double TotalPrice(this ProductPage page)
{
return page.Quantity * page.Price;
}
}Register the computed field in the configureConventions callback:
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice());After ProductPage syncs to Optimizely Graph, query TotalPrice using the following:
{
ProductPage {
items {
ProductName
Quantity
Price
TotalPrice
}
}
}Specify the indexing type for the computed field as the second parameter of IncludeField:
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.TotalPrice(), IndexingType.Queryable);Computed fields with service injection
Computed fields can accept an IServiceProvider parameter for accessing application services at indexing time. Pass null! for the parameter in the registration expression; the framework injects the real service provider at invocation time.
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();
}
}Register the computed field in the configureConventions callback:
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedChildCount());Async computed fields
For I/O-bound operations, computed fields can return Task<T>. Async methods must accept a CancellationToken parameter. Use default for the cancellation token in the registration expression.
public static class ProductPageComputedFields
{
public static Task<string> ComputedSlugAsync(
this ProductPage page,
CancellationToken cancellationToken = default)
{
return Task.FromResult(
page.ProductName?.ToLowerInvariant().Replace(' ', '-') ?? "untitled");
}
}Register the computed field in the configureConventions callback:
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedSlugAsync());Supported method signatures
The framework auto-detects IServiceProvider and CancellationToken parameters. The expression passed to IncludeField is used only as a method selector. Argument values are discarded and injected by the framework at invocation time.
The following method signatures are supported:
| Signature | Notes |
|---|---|
T Method() | Simple synchronous |
T Method(IServiceProvider sp) | With service injection |
Task<T> MethodAsync(CancellationToken ct) | Async |
Task<T> MethodAsync(IServiceProvider sp, CancellationToken ct) | Async with services |
static T Method() | Static method |
static T Method(this TContent content) | Extension method |
static T Method(this TContent content, IServiceProvider sp) | Extension with services |
static Task<T> MethodAsync(this TContent content, CancellationToken ct) | Async extension |
static Task<T> MethodAsync(this TContent content, IServiceProvider sp, CancellationToken ct) | Async extension with services |
Supported return types
The following return types are supported for computed fields. POCO (plain C# object) return types are covered in the POCO components section.
| Return type | Graph type | Example |
|---|---|---|
string | String | "hello" |
int, long | Int | 42 |
float, double, decimal | Float | 3.14 |
bool | Boolean | true |
DateTime | DateTime | DateTime.UtcNow |
DateTimeOffset | DateTime | DateTimeOffset.UtcNow |
XhtmlString | Rich text | Rendered HTML |
IEnumerable<T> | [T] | ["a", "b"] |
List<T> | [T] | [1, 2, 3] |
| POCO class | Component | Nested object |
List<TPoco> | [Component] | List of nested objects |
| Nullable value types | Same as base | int? → Int |
WarningUse simple, fast methods to avoid issues when syncing content to Optimizely Graph.
Use
IncludeFieldto add data to a content item that does not belong to CMS content. Do not return CMS data such asContentReference,ContentArea,Blob, orBlockDatabecause many features associated with those data types do not work with computed fields.
IncludeFielddoes not support methods that return the following CMS types:
BlobContentAreaContentAreaItemContentReferenceLinkItemLinkItemCollectionPageDataBlockDataMediaData
Computed fields and type inheritance
Computed fields configured on a content type automatically appear on all derived content types. Register a computed field on a base type to include it on every content type that inherits from it when indexed to Optimizely Graph.
public class BasePage : PageData { }
public class ArticlePage : BasePage
{
public virtual string Heading { get; set; }
}
public class NewsPage : BasePage
{
public virtual DateTime PublishDate { get; set; }
}
public static class BasePageComputedFields
{
public static string ComputedSection(this BasePage page) => "default";
}Register the computed field on the base type:
conventions.ForInstancesOf<BasePage>()
.IncludeField(p => p.ComputedSection());ArticlePage and NewsPage include ComputedSection in their indexed content without additional configuration. Query it on any of the derived types:
{
ArticlePage {
items {
Heading
ComputedSection
}
}
NewsPage {
items {
PublishDate
ComputedSection
}
}
}POCO components
When a computed field returns a POCO (plain C# object), the framework automatically registers it as a Graph component type and discovers its public properties.
public class ProductSummary
{
public string Title { get; set; } = "";
public decimal Price { get; set; }
public bool InStock { get; set; }
}
public static class ProductPageComputedFields
{
public static ProductSummary ComputedSummary(this ProductPage page) => new()
{
Title = page.ProductName,
Price = (decimal)page.Price,
InStock = true
};
}Register the computed field in the configureConventions callback:
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedSummary());After ProductPage syncs to Optimizely Graph, query the nested component:
{
ProductPage {
items {
ProductName
ComputedSummary {
Title
Price
InStock
}
}
}
}Control indexing on POCO properties
Control POCO property indexing with IndexingTypeAttribute:
public class ProductSummary
{
[IndexingType(IndexingType.Searchable)]
public string Title { get; set; } = "";
[IndexingType(IndexingType.Queryable)]
public decimal Price { get; set; }
}Lists of POCOs
Computed fields can also return lists of POCOs:
public class ProductTag
{
public string Name { get; set; } = "";
public int Weight { get; set; }
}
public static class ProductPageComputedFields
{
public static List<ProductTag> ComputedTags(this ProductPage page) =>
[
new() { Name = "electronics", Weight = 10 },
new() { Name = "sale", Weight = 5 }
];
}Multiple language support with computed fields
Give computed fields language-specific values by accessing the content item's language at indexing time. The following example returns a language-specific terms and conditions link.
[ContentType]
public class StandardPage : PageData { }
public static class StandardPageComputedFields
{
public static string TermsAndConditions(this StandardPage page)
{
var language = page.Language.Name;
var tncLink = "https://ourcorp/tnc";
var tnc = "Terms and conditions";
switch (language)
{
case "sv":
tnc = "villkor";
break;
}
return $"{tnc}: {tncLink}";
}
}Register the computed field in the configureConventions callback:
conventions.ForInstancesOf<StandardPage>()
.IncludeField(p => p.TermsAndConditions());When a StandardPage has multiple versions in different languages, the TermsAndConditions field returns a different value for each language version.
Set indexing type for fields
Use IndexingType to control how computed fields are indexed. Specify this as the second parameter of IncludeField.
conventions.ForInstancesOf<ProductPage>()
.IncludeField(p => p.ComputedTitle(), IndexingType.Searchable)
.IncludeField(p => p.TotalPrice(), IndexingType.Queryable);The following three indexing types are available for indexing conventions:
- Searchable – The property is searchable using full-text search. Applicable only to leaf properties of
XhtmlStringor primitive types (string,int,datetime, and so on). - Queryable – The property can be filtered using operators such as
eq,gt, andgte, but is not searchable when using full-text search. - Default – The system default indexing behavior is applied.
NoteTo set the indexing type for existing CMS properties (not computed fields), use the Admin UI under Settings > Content Types. Select the content type, then configure the Property Indexing Type for each property.
Best practices
- Use extension methods – Define computed fields as extension methods in a separate static class to keep indexing logic separate from the content model. This improves testability and avoids polluting content types with indexing concerns.
- Use descriptive method names – The method name becomes the field name in GraphQL (for example,
ComputedTitlebecomesComputedTitlein queries). - Keep computations lightweight – Computed fields run during indexing for every content item. Avoid expensive operations.
- Use async for I/O – If accessing external services or data, use async methods with
CancellationToken. - Default parameters for injected values – Use
= null!forIServiceProviderand= defaultforCancellationTokenin method signatures. - Avoid name collisions – Computed field names must not match existing CMS property names on the same content type.
- Prefer POCO components for structured data – Group related values into a POCO rather than adding many flat fields.
Updated 43 minutes ago
