---
title: Content types in code
slug: cms-12/docs/Gu_--content-types
docTags: 
createdAt: 2026-08-10T15:55:56.000Z
---

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

Content, content types, and content templates are linked together in the following way:

- A **content type** defines a set of *properties*, for example, name and publish date.
- A **content instance** is an instance of the .NET class defining the content type. When a content instance is created in edit view, values are assigned to the properties and stored in the database.
- A **template** (for example, an MVC controller) fetches the stored property values and renders the output.
- A template can be associated with the content type, to render the output in a certain context.

As shown in [Content](docId:-X6O47k4TDPtWW7YJukzw), there are several different base types or categories of content, like pages, blocks, and media in Optimizely Content Management System (CMS). Other products, like Optimizely Customized Commerce, have their base types of content. A specific implementation can then create several content types from the different content base types.

In the following, you find examples of how to model content types. [Content templates](docId\:Mx-5xc_AG37WCgBtdxeP3) describes how different content types can be rendered.

:::hint{type="info"}
**Note**
Standard MVC routing is not registered by default by CMS, but if it is used, renaming of a controller or action will affect the URL/route to the action.
:::

## Content types

In CMS, content types are usually defined in code as .NET classes inheriting from `IContentData` (or a suitable base class implementing  `IContentData` like `PageData`) and decorated with `ContentTypeAttribute`. There are different content base types in CMS, such as pages (base class `EPiServer.Core.PageData`), blocks (base class `EPiServer.Core.BlockData`) and media (interface `EPiServer.Core.IContentMedia`).

So to add a content type to CMS, add a .NET class to the project, inheriting the appropriate base class or interface (for pages, for example `PageData`) and decorate the class with `ContentTypeAttribute`. During initialization, the assemblies are scanned for .NET classes attributed with `ContentTypeAttribute`. For each of the classes found, a content type is created. For all public properties on the class, a corresponding property on the content type is created. The content type will then be available for editors to create an instance of.

### Create a content type

See [Get started with CMS](docId\:JyIa4i__kTSg02M0XfEYG) for information about creating a CMS project.

To add properties that you want to have for all page types (content types of base type `PageData`), we added an abstract `SitePageData` base class, which inherits from `EPiServer.Core.PageData`. This base class has an SEO property which we want used on all pages inheriting from the class.

**Example:** A simple article page type with a "Main Body" editor area of the [property type XhtmlString](docId\:uVkEPRLirysGAA0O8QIoc) type, inheriting from the `SitePageData` base class.

```csharp
using System;
using System.ComponentModel.DataAnnotations;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.SpecializedProperties;

namespace MyOptimizelySite.Models.Pages {
  [ContentType(DisplayName = "ArticlePage", GUID = "b8fe8485-587d-4880-b485-a52430ea55de", Description = "Basic page type for creating articles.")]
  public class ArticlePage: SitePageData {
    [CultureSpecific]
    [Display(
      Name = "Main body",
      Description = "The main body editor area lets you insert text and images into a page.",
      GroupName = SystemTabNames.Content,
      Order = 10)]
    public virtual XhtmlString MainBody {
      get;
      set;
    }

  }
}
```

**Example:** The `SitePage` base class, with the SEO property.

```csharp
namespace MyOptimizelySite.Models.Pages {
  public abstract class SitePageData: EPiServer.Core.PageData {
    [Display(GroupName = "SEO", Order = 200, Name = "Search keywords")]
    public virtual String MetaKeywords {
      get;
      set;
    }
  }
}
```

A recommendation is to assign a unique GUID for the `ContentTypeAttribute` for all types; this makes it possible to rename or move the class later. Also, note that content types implicitly contain a set of *built-in properties* available for all content instances, regardless of the content type instance. See [Content Metadata properties](docId:-SIFNlk9RvhXw8Z_337Qe) for built-in metadata properties.

Because the rendering template has not yet been created, pages based on this page type cannot be edited from the **On-Page** edit view, only from the **All Properties** edit view.

:::hint{type="info"}
**Note**
Why are the properties declared as **virtual** here? What happens in the background is that a proxy class is created for the page type. The proxy will override the properties with an implementation, and this only works if properties are declared as virtual. If the properties are not declared virtual, you need to implement get/set so that these will read/write data to the underlying property collection instead.
:::

### Custom content base types

Normally when creating a content type, it is based on an existing base type (like `PageData`, `BlockData`, `IContentMedia` and so on). Then the content type will automatically be available in edit view, for example, pages are available for creation from the page tree, while blocks and media are created from the assets panel. However, you can define a custom content type that is not based on any existing base type, like in [Persisting IContent instances](docId\:cAHoSLS_sP5GiO2ANkXRc). A custom-defined content type that does not belong to a known base type will not be available to create from edit view by default. This might be fine if the usage is only programmatically, but if you want a custom-based content type to be available in edit view, you must extend the user interface. See [Describing content in the UI](docId\:SMDlj1xiH46VevC7BVSHg) for an example of extending the user interface for additional content roots.

## Blocks

Blocks in CMS differ from other content types in that they can be used as reusable smaller content parts, which editors can add to pages. You can use blocks or lists of blocks in code as an instance or list property on a content model (for example, a page). This section explains the concept of blocks, block types, and templates

Blocks, block types, and block templates are linked together in the following way:

- A  **block type**defines a set of properties, for example, a heading and a page listing.
- A **block** is an instance of the .NET class defining the block type.
- Associated **view components** and **views** render the block in some context.

Blocks can only be rendered in the context of other content, such as a page. A block instance is part of a content instance (if a `ContentType` contains an instance or list property or a list of the block type), or a shared instance.

- For an instance or list instance or list property on a content **instance**, the blocks are stored, loaded, and versioned as part of the content instance.
- For a **shared block**, the blocks are stored, loaded, and versioned individually as an entity, and can be referenced from multiple pages or blocks.

### Block types

In CMS, block types are usually defined in code as classes based on a model inheriting from `EPiServer.Core.BlockData`, in a similar fashion as for page types. During initialization, the assemblies are scanned for .NET classes inheriting `BlockData` and decorated with `ContentTypeAttribute`. The `BlockData` object is the programmatic representation of a block, containing the properties defined in your .NET class. The value of `currentBlock` is automatically set to the `BlockData` object that the client requests.

For each of the classes found, a block type is created. For all public properties on the class, a corresponding property on the block type is created.

### Create a block type

**Example:** A simple block type with properties for a heading and a link to an image. `TeaserBlock` inherits from `BlockData`, which inherits from `EPiServer.Core.BlockData`.

```csharp
using System;
using System.ComponentModel.DataAnnotations;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.Web;

namespace MyOptimizelySite.Models.Blocks {
  [ContentType(DisplayName = "TeaserBlock",
    GUID = "38d57768-e09e-4da9-90df-54c73c61b270",
    Description = "Heading and image.")]
  public class TeaserBlock: BlockData {
    [CultureSpecific]
    [Display(Name = "Heading",
      Description = "Add a heading.",
      GroupName = SystemTabNames.Content,
      Order = 1)]
    public virtual String Heading {
      get;
      set;
    }

    [Display(Name = "Image", Description = "Add an image (optional)",
      GroupName = SystemTabNames.Content,
      Order = 2)]
    public virtual ContentReference Image {
      get;
      set;
    }
  }
}
```

As for page types, you should assign a unique GUID for the block type to support future renaming.

Blocks will only be editable from the **All Properties** edit view, and can only be previewed in the context of some other content like a page. However, you can add specific preview rendering for blocks, allowing editors to edit and preview them in the **On-Page** edit view.
