Manage display templates
Display templates let content managers style Visual Builder nodes without changing code, while developers control the available styles. Developers define the templates and their settings, and content managers choose among them in the editor. To learn how display templates fit into the layout and composition system, see Configure Visual Builder. For the editor perspective, see Select Visual Builder styles.
CMS gives you three ways to manage display templates:
- IDisplayTemplateRepository – Manages display templates in code from inside the CMS application. Use it to define templates in your solution and provision them at startup.
- CMS REST API – Manages display templates from outside the application, such as a deployment pipeline or an external tool, through the displaytemplates endpoints.
- Mark (Optimizely Agent Platform) – Manages display templates through conversational tools that wrap the same operations. See Available tools.
All three act on the same data. A template you create with one method is visible to the other two.
Display template structure
A display template consists of the following:
- Key – The unique identifier of the template. The key must be two to 255 characters, start with a letter, and contain only letters, digits, and underscores.
- Display name – The name shown to content managers in the editor.
- Scope – The single type association that controls where the template is available. See Scope a display template.
- Default – Whether CMS preselects the template for a node of the associated type. CMS uses the first template it finds with the default flag set. It seeds each setting with a default value (the first choice for a select setting, false for a checkbox setting).
- Settings – An ordered list of settings. Each setting has a key, a display name, an editor, a sort order, and, for select editors, a list of choices.
Scope a display template
Associate each template with exactly one of the following. Saving a template with no association or with more than one association throws a ValidationException.
- Base type – Makes the template available to every content type that derives from that base type.
- Content type – Makes the template available to one specific content type.
- Node type – Makes the template available to layout nodes, which have no content type of their own. The values are row and column. In code, use the CompositionNodeType.Row and CompositionNodeType.Column constants in the EPiServer.VisualBuilder namespace.
Note The .NET API and the REST API use different names for the same base type. Components are the clearest case: the .NET base type is Block, and the REST API calls it _component. Validation fails when you send a REST value to the .NET API, or a .NET value to the REST API.
The following table maps each base type to its .NET constant and its REST value.
VisualBuilderContentTypeBase is in the EPiServer.VisualBuilder namespace, and ContentTypeBase is in EPiServer.DataAbstraction. Elements extend blocks, so a template scoped to elements uses the block base type in code. The REST API also accepts other base types, such as _page and _media. Those content types have no Visual Builder layout to style.
Setting editors
The editor value tells the editor UI how to render the setting. The DisplaySettingEditor class in EPiServer.DataAbstraction defines the supported values:
- DisplaySettingEditor.Select (select) – A drop-down list. The content manager picks one of the setting's choices.
- DisplaySettingEditor.Checkbox (checkbox) – A toggle that stores true or false. CMS ignores choices for this editor.
CMS validates the values a content manager selects against the template. The content reports a validation warning when a value matches no defined choice. A non-boolean value on a checkbox setting produces the same warning.
Manage display templates in code
AddCms() registers IDisplayTemplateRepository in the service container. The interface is in the EPiServer.DataAbstraction namespace. Inject it like any other CMS service:
using EPiServer.DataAbstraction;
public class SectionStyleService(IDisplayTemplateRepository displayTemplateRepository)
{
// ...
}The interface has the following members:
- List() – Returns every display template.
- Load(int id) – Returns the template with the given ID, or null.
- Load(string key) – Returns the template with the given key, or null. The key comparison ignores case.
- Save(DisplayTemplate template) – Creates or updates a template.
- Delete(int id) – Deletes the template with the given ID.
- Delete(string key) – Deletes the template with the given key.
- FindByNodeType(string nodeType) – Returns the templates associated with a node type, ordered by name.
- FindByBaseType(ContentTypeBase baseType) – Returns the templates associated with a base type, ordered by name.
- FindByContentType(int contentTypeID) – Returns the templates associated with a content type and with its base type, ordered by name.
Create a display template
Set a key, a display name, and one type association, then call Save. The following hosted service creates a section template at application startup. The template has a color scheme setting and a highlight toggle.
using EPiServer.DataAbstraction;
using EPiServer.VisualBuilder;
using Microsoft.Extensions.Hosting;
public class DisplayTemplateProvisioner(IDisplayTemplateRepository displayTemplateRepository)
: IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
if (displayTemplateRepository.Load("DefaultSection") is not null)
{
return Task.CompletedTask;
}
displayTemplateRepository.Save(new DisplayTemplate
{
Key = "DefaultSection",
Name = "Default section",
BaseType = VisualBuilderContentTypeBase.Section,
IsDefault = true,
Settings =
[
new DisplaySetting
{
Key = "colorScheme",
Name = "Color scheme",
Editor = DisplaySettingEditor.Select,
SortOrder = 10,
Choices =
[
new DisplaySettingChoice { Key = "default", Name = "Default", SortOrder = 10 },
new DisplaySettingChoice { Key = "primary", Name = "Primary", SortOrder = 20 },
new DisplaySettingChoice { Key = "secondary", Name = "Secondary", SortOrder = 30 }
]
},
new DisplaySetting
{
Key = "highlight",
Name = "Highlighted",
Editor = DisplaySettingEditor.Checkbox,
SortOrder = 20
}
]
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}Register the hosted service after AddCms():
services.AddHostedService<DisplayTemplateProvisioner>();CMS registers its own startup work as IHostedLifecycleService. The host completes every StartingAsync call before it runs any StartAsync call. As a result, the repository is ready when your StartAsync runs, regardless of registration order.
Treat templates, settings, and choices as abstractions. For example, name a choice primary rather than red. Abstract names stay meaningful across delivery channels and survive brand color changes.
Update a display template
Load and List return read-only instances, which the repository caches. Call CreateWritableClone before you change anything, then save the clone. Saving a read-only instance throws an ArgumentException.
var template = displayTemplateRepository.Load("DefaultSection");
if (template is null)
{
return;
}
var writableTemplate = template.CreateWritableClone();
writableTemplate.Name = "Standard section";
writableTemplate.Settings.Add(new DisplaySetting
{
Key = "verticalSpacing",
Name = "Vertical spacing",
Editor = DisplaySettingEditor.Select,
SortOrder = 30,
Choices =
[
new DisplaySettingChoice { Key = "compact", Name = "Compact", SortOrder = 10 },
new DisplaySettingChoice { Key = "roomy", Name = "Roomy", SortOrder = 20 }
]
});
displayTemplateRepository.Save(writableTemplate);Note the following about updates:
- Save writes the settings list as given. It deletes any existing setting that is missing from the list.
- Save skips the write when the template exists and nothing changed.
- Deleting a setting or a choice that content already uses does not rewrite that content. The stored value remains until someone saves the content again. The content reports a validation warning in the meantime.
Find display templates
Retrieve templates with List or one of the Find methods:
var allTemplates = displayTemplateRepository.List();
var rowTemplates = displayTemplateRepository.FindByNodeType(CompositionNodeType.Row);
var sectionTemplates = displayTemplateRepository.FindByBaseType(VisualBuilderContentTypeBase.Section);
var templatesForContentType = displayTemplateRepository.FindByContentType(contentType.ID);FindByContentType returns templates scoped to the content type and to its base type. This set matches what the editor offers for a node of that content type.
Delete a display template
Call Delete with the template key or ID:
displayTemplateRepository.Delete("DefaultSection");Both Delete overloads throw an ArgumentException when no matching template exists.
Validation rules
Save throws a ValidationException when any of the following is true:
- The key is missing, shorter than two characters, longer than 255 characters, or does not match the pattern ^[A-Za-z][_0-9A-Za-z]+$.
- The display name is missing or longer than 255 characters.
- The template has no type association, or more than one.
- BaseType does not reference an existing type, or ContentTypeID does not reference an existing content type.
- The key of a template being created is already in use.
- Two settings in the template share a key, or two choices in a setting share a key.
React to display template changes
The repository publishes an event on every change. The events are in the EPiServer.DataAbstraction namespace, and each carries the affected DisplayTemplate:
- DisplayTemplateCreatedEvent
- DisplayTemplateUpdatedEvent
- DisplayTemplateDeletedEvent
All three derive from the abstract DisplayTemplateEvent record. Subscribe to the base type to handle every change with one subscriber:
public class DisplayTemplateSubscriber : IEventSubscriber<DisplayTemplateEvent>
{
public Task HandleAsync(
DisplayTemplateEvent eventData,
EventContext context,
CancellationToken cancellationToken = default)
{
// Respond to the change, for example by clearing a rendering cache.
return Task.CompletedTask;
}
}Register the subscriber in the service container:
services.AddCmsEventSubscriber<DisplayTemplateEvent, DisplayTemplateSubscriber>();For details about event subscribers, see Send and receive custom events.
Manage display templates with the REST API
The CMS REST API exposes display templates at /_cms/v1/displaytemplates. For the base address, OAuth client setup, and access tokens, see Introduction to the CMS REST API and Authentication and authorization.
The API applies the same validation rules as the repository.
Create a template
Send a POST request with the template in the request body. A successful request returns 201 Created. A key already in use returns 409 Conflict.
POST https://example.com/_cms/v1/displaytemplates
Content-Type: application/json
{
"key": "DefaultSection",
"displayName": "Default section",
"baseType": "_section",
"isDefault": true,
"settings": {
"colorScheme": {
"displayName": "Color scheme",
"editor": "select",
"sortOrder": 10,
"choices": {
"default": { "displayName": "Default", "sortOrder": 10 },
"primary": { "displayName": "Primary", "sortOrder": 20 },
"secondary": { "displayName": "Secondary", "sortOrder": 30 }
}
},
"highlight": {
"displayName": "Highlighted",
"editor": "checkbox",
"sortOrder": 20
}
}
}For the full request schema, see Create display template.
List templates
Send a GET request. The response is a paged collection. Use the pageIndex and pageSize query parameters to page through the results.
GET https://example.com/_cms/v1/displaytemplates?pageIndex=0&pageSize=50For the full response schema, see List display templates.
Get a template
Send a GET request with the template key:
GET https://example.com/_cms/v1/displaytemplates/DefaultSectionThe endpoint supports the If-None-Match and If-Modified-Since request headers for conditional requests.
For the full response schema, see Get display template.
Update a template
Send a PATCH request with the application/merge-patch+json content type and only the fields you want to change.
PATCH https://example.com/_cms/v1/displaytemplates/DefaultSection
Content-Type: application/merge-patch+json
{
"displayName": "Standard section",
"settings": {
"verticalSpacing": {
"displayName": "Vertical spacing",
"editor": "select",
"sortOrder": 30,
"choices": {
"compact": { "displayName": "Compact", "sortOrder": 10 },
"roomy": { "displayName": "Roomy", "sortOrder": 20 }
}
}
}
}Use the If-Match or If-Unmodified-Since request headers to avoid overwriting a concurrent change.
For the full request schema, see Patch display template.
Delete a template
Send a DELETE request with the template key:
DELETE https://example.com/_cms/v1/displaytemplates/DefaultSectionThe endpoint supports the If-Match and If-Unmodified-Since request headers.
For the full response details, see Delete display template.
Field names in code and in the REST API
The .NET model and the REST representation describe the same template but do not use the same names or shapes.
How display templates reach the delivery site
Optimizely Graph indexes the applied template and the selected settings with the content. Every node in the composition carries a displayTemplateKey and a displaySettings array. The delivery site reads these values to render the content. For the indexed structure, see Configure Visual Builder.
Export packages include display templates, so a template you define in one environment transfers with an export and import.