---
title: TemplateDescriptor and tags
slug: cms-12/docs/templatedescriptor-and-tags
docTags: 
createdAt: 2026-08-10T15:55:55.000Z
---

`TemplateDescriptor` lets you add metadata when registering templates for content types, and you can use tags to control the rendering of objects in a content area.

:::hint{type="info"}
**Note**
The examples are based on ASP.NET Core.
:::

## TemplateDescriptor attribute

A page or a block can have multiple associated templates, for example, one for a web channel and one for a mobile device. Pages can also have a **partial template** used, for example, when the page is displayed inside the content area of another page.

`TemplateDescriptor` is an optional attribute that registers templates and is involved when the system determines which template to use when rendering a content instance in a specific context. See [Render content](docId\:N9EhLwAoRUYvRy2ef0Rpr) and  [Select templates](docId\:EiFmxwOGotchn4mtlg3wr) for information on how templates are registered and selected. Use the attribute to add metadata such as template path, inheritance, content type model, and description.

### Properties

The attribute is found in the `EPiServer.Framework.DataAnnotations` namespace and some of its properties are described below.

- `Path` – The path to the rendering template must only be set if the folder structure **does not** follow the standard MVC convention. The default is null.
- `ModelType` – The model to which the template applies. The model type can be set to an interface or abstract type if the template should be registered for multiple concrete types. The default is null.
- `Description` – Contains a description of the template. The default is null.
- `Inherited` – When set to **true**, model types that inherit from the `ModelType` get the template as a supported template. The default is false.
- `Tags` – An optional list of tags for which this template is registered.
- `AvailableWithoutTag` – Specifies if this template is available when tags defined in `Tags` are not present in context.
- `TemplateTypeCategory` – Optional value to specify the template type. If not specified, the template type is resolved from `ModelType`.

### Behavior

If `TemplateDescriptor` is:

- **not present**, template inheritance is **true** by default.
- **present but without specified parameters**, the default values above apply.
- **present with inherited set to true**, the template is inherited by types assignable to `ModelType`. This is useful if you need a fallback template for content types without specific templates.

### Concrete type example

Assume you have a `ArticlePage` content type :

```csharp
using EPiServer.Core;

namespace MyOptimizelySite.Models.Pages {
  [ContentType]
  public class ArticlePage: PageData {
    public virtual string Heading {
      get;
      set;
    }
  }
}
```

The template (controller) for the  `ArticlePage` content type, with the `TemplateDescriptor` attribute present. Inherited is set to false, meaning that this template is used as the default template for the `ArticlePage` content type and no inheritance will take place.

```csharp
using EPiServer;
using EPiServer.Core;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using MyOptimizelySite.Models.Pages;

namespace MyOptimizelySite.Controllers {
  [TemplateDescriptor(
    Inherited = false,
    Description = "Default template to be used by Article pages")]
  public class ArticlePageController: PageController<ArticlePage> {
    public ActionResult Index(ArticlePage currentPage) {
      // Implementation of action view the page. 
      return View(currentPage);
    }
  }
}
```

### Partial page rendering example

The following example shows how to use `TemplateDescriptor` for defining a partial page renderer when rendering a page inside a content area.

Assume you have the following `SitePageData` base class with a summary (`MetaKeywords` string) and an image:

```csharp
using EPiServer.Core;

namespace MyOptimizelySite.Models.Pages {
  public abstract class SitePageData: PageData {
    public virtual string MetaKeywords {
      get;
      set;
    }
    public virtual ContentReference Image {
      get;
      set;
    }
  }
}
```

You have a **partial page component** decorated with `TemplateDescriptor` with `Inherited=true`, to render page partials for pages inheriting from `SitePageData`. The controller selects a view in a folder specified by the namespace convention.

```csharp
using AlloyTemplates.Models.Pages;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using Microsoft.AspNetCore.Mvc;

namespace MyOptimizelySite.ViewComponents {
  [TemplateDescriptor(Inherited = true)]
  public class PagePartialComponent: PartialContentComponent<SitePageData> {
    protected override IViewComponentResult InvokeComponent(SitePageData currentContent) {
      return View("/Views/Shared/PagePartials/PagePartial.cshtml", currentContent);
    }
  }
}
```

The **partial view** has an if-else construct checking if the model has a template for the content. Any page inheriting from the `SitePageData` is rendered displaying the page name with a link, the `MetaKeywords` string, and an image when added to the content area of another page.

```csharp
@using EPiServer.Core
@model SitePageData
    
<div class="block span2">
  <div class="border">
    @if (Model.HasTemplate())
    {
      <a href="@Url.ContentUrl(Model.ContentLink)">
        <h3>@Model.PageName</h3>
        <p>@Html.PropertyFor(x => x.MetaKeywords)<p>
        <img src="@Url.ContentUrl(Model.Image)" />
      </a>
    }
    else
    {
      <h3>@Model.PageName</h3>
    }
  </div>
</div>
```

### Pages without controllers example

An example of how you can handle page types that do not have their specific controllers by specifying `Inherited=true`, and dynamically selecting template.

```csharp
[TemplateDescriptor(Inherited = true)]
public class DefaultPageController: PageController<SitePageData> {
  public ViewResult Index(SitePageData currentPage) {
    return View($"~/Views/{currentPage.GetOriginalType().Name}/Index.cshtml", currentPage);
  }
}
```

## Tags

You can apply **tags** to be used in the rendering selection. When a template is associated with a tag, then that template is used only when the calling context, such as `Property` or `PropertyFor` in a view, has a matching tag. You can also have different content areas render the same content differently using tags or display options. If you have active [display channels](docId\:DOH7by8bAqGeC4FnaZciq) for your content types, the `ChannelName` will act as a tag in the rendering selection.

### Register multiple templates example

Assume you have a model containing a teaser block with a heading and an image.

```csharp
using EPiServer.Core;

namespace MyOptimizelySite.Models.Blocks {
  [ContentType]
  public class TeaserBlock: BlockData {
    public virtual string Heading {
      get;
      set;
    }
    public virtual ContentReference Image {
      get;
      set;
    }
  }
}
```

- Register two templates for the teaser block to display differently depending on the context.
- Add two sidebar templates (left and right) for displaying the block in the sidebar area of web pages with this.
- Add a template for the standard block, part of the content model. The blocks are displayed in a content area of the start page for the website.

See [Content templates](docId\:CFs8ImRh2OZ6H9aZTk0W2) for information regarding blocks. In this case, you register partial views without controllers, so use `EPiServer.Web.Mvc.IViewTemplateModelRegistrator` to register your templates. In the *Business* folder of your project, create a `ViewTemplateModelRegistrator` class inheriting from `IViewTemplateModelRegistrator`, and add the desired templates and tags.

The following code shows the template registration class using `IViewTemplateModelRegistrator`:

```csharp
namespace MyOptimizelySite.Business {
  public class ViewTemplateModelRegistrator: IViewTemplateModelRegistrator {
    public void Register(TemplateModelCollection viewTemplateModelRegistrator) {
      viewTemplateModelRegistrator.Add(typeof (TeaserBlock),
        new EPiServer.DataAbstraction.TemplateModel() {
          Name = "SidebarTeaserRight",
            Description = "Displays a teaser for a page.",
            Path = "~/Views/Shared/SidebarTeaserBlockRight.cshtml",
            AvailableWithoutTag = true
        },
        new EPiServer.DataAbstraction.TemplateModel() {
          Name = "SidebarTeaserLeft",
            Description = "Displays a teaser for a page.",
            Path = "~/Views/Shared/SidebarTeaserBlockLeft.cshtml",
            Tags = new string[] {
              RenderingTags.Sidebar
            }
        });

      viewTemplateModelRegistrator.Add(typeof (StandardBlock),
        new EPiServer.DataAbstraction.TemplateModel() {
          Name = "SidebarTeaser",
            Description = "Displays a teaser of a page.",
            Path = "~/Views/Shared/StandardBlock.cshtml",
            Tags = new string[] {
              RenderingTags.Sidebar
            }
        });
    }
  }
}
```

The following code shows the `SidebarTeaserBlockRight` partial view for the teaser block (there is an identical `SidebarTeaserBlockLeft` for the left one):

```csharp
@model MyOptimizelySite.Models.Blocks.TeaserBlock
    
<div>
  <h2>@Html.PropertyFor(x => x.Heading)</h2>
  <img src="@Url.ContentUrl(Model.Image)" />
</div>
```

The following code shows the rendering view for the start page, where the blocks are displayed:

```csharp
@model MyOptimizelySite.Models.Pages.StartPage
    
<div>
  @Html.PropertyFor(m => m.MainBody)
</div>
    
<div>
  @Html.PropertyFor(m => m.GeneralContentArea, new { Tag = "Sidebar" })
</div>
```

How tagging is used in template selection:

- If the `AvailableWithoutTag` attribute for a template is set to **true**, the template is applied regardless of whether the rendering context has a corresponding tag.
- If the `AvailableWithoutTag` is set to **false**, or does not exist, the template is not applied unless the rendering context has a corresponding tag\*.\*

In this scenario, the following happens:

- The start page `GeneralContentArea` has the `RenderingTags.Sidebar` tag, which means that only templates with this tag are applied, and without the `AvailableWithoutTag` or where this is set to **false**.
- The `SidebarTeaserLeft` template has a matching tag and `AvailableWithoutTag` set, and is applied. This is also valid for the template used for the Standard block.
- The `SidebarTeaserRight` template has a matching tag, and `AvailableWithoutTag = true`. This template is applied even if the `RenderingTags.Sidebar` tag would be removed from the content area.
