---
title: Media types and templates
slug: cms-11/dev-docs/media-types-and-templates
docTags: 
createdAt: 2026-07-22T19:45:18.312Z
---

:::hint{type="info"}
**Note**
Media types must first be defined in your content model for built-in asset functionality such as file upload or drag-and-drop to work in edit view. It is recommended that you create specific classes for media of type images, video, and anything generic other than video or image, such as text or PDF files.
:::

Media and media types work as follows:

- A *media type* defines a set of properties.
- A media is an instance of the .NET class that defines the media type.
- When an editor creates or uploads media, values are assigned to the properties defined by the media type.
- When a visitor requests an instance of media, the default media handler sends the binary data to the visitor.

During initialization, Visual Studio scans assemblies in the `bin` folder  for .NET classes decorated with the `[ContentType]` attribute, and inheriting from `MediaData`. Optimizely Content Management System (CMS) defines metadata for `media` as properties in the `media` class, which you can edit from edit view.

## Generic media type

You can use the Visual Studio extensions to create media types. The example below shows a generic media type for handling files, inheriting from `MediaData`, with a description property available under the **Content** tab in edit view.

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

namespace MyEpiserverSite.Models.Media {
  [ContentType(DisplayName = "GenericMedia", GUID = "89761dbb-bf22-4cee-93c7-9d661d75cad8", Description = "Used for generic file types such as Word or PDF documents.")]
  [MediaDescriptor(ExtensionString = "pdf,doc,docx")]
  public class GenericMedia: MediaData {
    [CultureSpecific]
    [Editable(true)]
    [Display(
      Name = "Description",
      Description = "Add a description of the content.",
      GroupName = SystemTabNames.Content,
      Order = 1)]
    public virtual String Description {
      get;
      set;
    }
  }
}
```

### MediaDescriptor attribute

The `MediaDescriptor` attribute defines a list of file extensions and associates specific file types with a given content type, creating content of the correct type when a user uploads media through the user interface.

When you create media content from the server side, you also can have this same content type resolved by using the `ContentMediaResolver`.

## Specialized media types

`ImageData` and `VideoData` are specialized base classes distinguishing images and videos from other generic media to apply special handling in edit view. For example, media types inheriting ImageData will display image *thumbnails* when listing images in the **Media** folder in edit view. `ImageData` and `VideoData` inherit from `MediaData`.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/JyYiVBRwISATLeKjXhTzm-20260722-194635.png)

**Example:** Media type for images, inheriting from `ImageData`, and with properties for copyright and a description.

```csharp
namespace MyEpiserverSite.Models.Media {
  [ContentType(DisplayName = "ImageFile", GUID = "875b3b51-e0a7-412c-8f56-44f59c184440", Description = "Used for images of different file formats.")]
  [MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")]
  public class ImageFile: ImageData {
    //// <summary>
    /// Gets or sets the copyright.
    /// </summary>
    public virtual string Copyright {
      get;
      set;
    }
    public virtual String Description {
      get;
      set;
    }
  }
}
```

The `UIHint` property attribute is used to select either editor, renderer, or both by defining a hint string. You can use `EPiServer.Web.UIHint` for known types in the system, for instance `UIHint.Image`.

**Example:** Media type for videos, inheriting from `VideoData`, and with properties for copyright and link to video preview image.

```csharp
namespace MyEpiserverSite.Models.Media {
  [ContentType(DisplayName = "VideoFile", GUID = "f2285e5a-be15-47b6-8952-e3c61deaefd2", Description = "Used for specific video file formats.")]
  [MediaDescriptor(ExtensionString = "flv,mp4,webm")]
  public class VideoFile: VideoData {
    /// <summary>
    /// Gets or sets the copyright.
    /// </summary>
    public virtual string Copyright {
      get;
      set;
    }

    /// <summary>
    /// Gets or sets the URL to the preview image.
    /// </summary>
    [UIHint(UIHint.Image)]
    public virtual ContentReference PreviewImage {
      get;
      set;
    }
  }
}
```

## ImageDescriptor attribute

The `ImageDescriptor` attribute automatically generates scaled images. When you route to a BLOB type property, CMS determines whether the BLOB is null and the property has an `ImageDescriptor` attribute. If both are true, `IBinaryStorable.BinaryData` automatically generates a scaled image.

**Example:** The `ImageData.Thumbnail` property with an `ImageDescriptor` attribute.

```csharp
/// <summary>
/// Base class for content types which should be handled as images by the system.
/// </summary>
public class ImageData: MediaData {
  /// <summary>
  /// Gets or sets the generated thumbnail for this media.
  /// </summary>
  [ImageDescriptor(Width = 48, Height = 48)]
  public override Blob Thumbnail {
    get {
      return base.Thumbnail;
    }
    set {
      base.Thumbnail = value;
    }
  }
}
```

## Media structure

Media is structured using folders. A folder in the media structure can have other folders or media as children, but a media instance cannot have any children. You can set access rights on folders to control editors' availability.

The following criteria define the media structure:

- Set the **global** media root folder as `GlobalAssetsRoot`, defining media available for content on sites in a mmulti-sitescenario.
- Set a **site-specific** media root folder as `SiteAssetsRoot`, defining media only available for a specific site in a multi-site scenario. In a single-site scenario, the `GlobalAssetsRoot` and `SiteAssetRoot` will typically point to the same folder.
- A **folder** is an instance of `ContentFolder` and is used to structure content. A content folder does not have an associated rendering and will not appear on the site.

See  `SiteDefinition`  in the [Optimizely CMS class library](https://world.optimizely.com/documentation/class-libraries/cms/).

## Change the maximum upload file size

In *web.config*, change the parameters `maxAllowedContentLength` and `maxRequestLength`:

```xml
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="1048576000" />
    </requestFiltering>
  </security>
</system.webServer>
```

## Customize the media handler

The built-in `EPiServer.Web.ContentMediaHttpHandler` delivers media by default. However, if you want custom processing before sending the media to the visitor, the example below shows how to implement your HTTP handler. Handlers for media use the same templating system as other content types in Optimizely, which means you can use any template for media; you are not limited to HTTP handlers.

```csharp
using System;
using System.Web;
using EPiServer.Core;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using EPiServer.Web.Routing;

namespace AlloyTemplates.Business.Initialization {
  public class CustomFileHandler: IHttpHandler, IRenderTemplate<CustomFile> {
    private readonly IContentRouteHelper _contentRouteHelper;
    private readonly IAccessDeniedHandler _accessDeniedHandler;

    // we need to provide a parameterless constructor
    public CustomFileHandler(IContentRouteHelper contentRouteHelper, IAccessDeniedHandler accessDeniedHandler) {
      _contentRouteHelper = contentRouteHelper;
      _accessDeniedHandler = accessDeniedHandler;
    }

    public CustomFileHandler(): this(ServiceLocator.Current.GetInstance<IContentRouteHelper>(), ServiceLocator.Current.GetInstance<IAccessDeniedHandler>()) {}

    public bool IsReusable => false;

    public void ProcessRequest(HttpContext context) {
      //Get file content
      var content = _contentRouteHelper.Content;

      if (content == null) {
        throw new HttpException(404, "Not Found.");
      }

      //Check access
      if (!content.QueryDistinctAccess(AccessLevel.Read)) {
        _accessDeniedHandler.AccessDenied(new HttpContextWrapper(context));
        return;
      }

      //Cast to custom file
      var customFile = content as CustomFile;
      if (customFile?.BinaryData == null) {
        throw new HttpException(404, "Not Found.");
      }

      //Set caching policy
      context.Response.Cache.SetCacheability(HttpCacheability.Private);
      context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1));

      //Do custom processing
      //TransmitProcessedFile(customFile.BinaryData)
    }
  }
}
```

You can base your custom transmitter implementation on `EPiServer.Web.BlobHttpHandler` or `EPiServer.Web.MediaHandlerBase`, and use the `TransmitFile` method for transmittance of file data.
