---
title: List properties
slug: cms-13/docs/list-properties
createdAt: 2026-07-30T20:22:00.693Z
---

Most property types in Optimizely Content Management System (CMS) can be modeled as lists, including types modeled as block types. For example, given that there is a block type modeled as:

```csharp
[ContentType(AvailableInEditMode = false, GUID = "38d57768-e09e-4da9-90df-54c73c61b270")]  
public class ContactBlock : BlockData {  
   //block properties  
}
```

Then, other Content models can define lists of the block type as:

```csharp
public virtual IList<ContactBlock> Contacts { 
  get; 
  set; 
}
```

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

Each list item looks the same when collapsed: just the type name. Override this by specifying which property renders the list item header.

```csharp
[ListItemHeaderProperty(nameof(Blocks.ContactBlock.Heading))]
public virtual IList<ContactBlock> Contacts { 
  get; 
  set; 
}
```

The idea is the same as with `List<T>`. Add the list as a property to your model, and the default descriptors match the `IList` type and render the correct editor. For example:

```csharp
public virtual IList<string> ListOfStrings { 
  get; 
  set; 
}
```

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/7SowVdJgs6ntGbEnqxmJh-20260722-184759.png)

```csharp
public virtual IList<int> ListOfIntegers { 
  get; 
  set; 
}
```

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/4WwYQ8gLOf-BuyPh8xmtS-20260722-184759.png)

```csharp
public virtual IList<DateTime> ListOfDates { 
  get; 
  set; 
}
```

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

```csharp
public virtual IList<XhtmlString> ListOfXhtml { 
  get; 
  set; 
}
```

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/yLLps8NvquztK2C-yf4PO-20260722-184759.png)

Define a list property with a custom type inside. Do not use the custom type as a block in the system. Only an editor and descriptor for a single property are needed; the system applies them when the property appears within a list. For example, if there is a custom editor for a type `GeoCoordinate` defined like this:

```csharp
public virtual IList<GeoCoordinate> Contacts { 
  get; 
  set; 
}
```

...where `GeoCoordinate` is...

```csharp
public class GeoCoordinate {
  public decimal Latitude {
    get;
    set;
  }
  public decimal Longtitude {
    get;
    set;
  }
  public int Zoom {
    get;
    set;
  }
}
```

...and then there is a custom type descriptor for `GeoCoordinate` which defines a custom editor...

```csharp
[EditorDescriptorRegistration(TargetType = typeof (GeoCoordinate))]
public class GeoCoordinateEditorDescriptor: EditorDescriptor {
  public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes) {
    ClientEditingClass = "alloy/editors/GoogleMapsPicker";

    base.ModifyMetadata(metadata, attributes);
  }
}
```

...`GeoCoordinate` must inherit from `BlockData`, or you must provide a custom `BackingType`.

```csharp
[ContentType(AvailableInEditMode = false, GUID = "11d57768-e09e-4da9-90df-54c73c61b270")]
public class GeoCoordinate: BlockData {
  public virtual decimal Latitude {
    get;
    set;
  }
  public virtual decimal Longtitude {
    get;
    set;
  }
  public virtual int Zoom {
    get;
    set;
  }
}
```

:::hint{type="info"}
**Note**
The `AvailableInEditMode` attribute and `virtual` modifier are added to all model properties.
:::

Provide a custom `UIHint` to the list item. For example, create a list of images or video files:

```csharp
[UIHint(UIHint.Image)]
public virtual IEnumerable<ContentReference> Images { 
  get; 
  set; 
}
```

...which is rendered like this:

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

:::hint{type="info"}
**Note**
The property type does not have to be `IList`. The Optimizely descriptors match the `IList` interface and all its base interfaces, so `IEnumerable` or `ICollection` also work. All the following declarations are matched by the Value List descriptor.
:::

```csharp
public virtual IEnumerable<string> EnumerableStrings {
  get;
  set;
}
public virtual ICollection<string> CollectionOfStrings {
  get;
  set;
}
public virtual IList<string> ListOfStrings {
  get;
  set;
}
```

## Validate a list

Apply validation to the list itself and to individual items. Use `ListItemsAttribute` to control the number of items in the list:

```csharp
[ListItems(5)]
public virtual IList<int> Max5Items { 
  get; 
  set; 
}
```

Apply the following attributes to individual items:

```csharp
[ItemRangeAttribute(1, 10)]
public virtual IList<int> ItemsBetween1And10 { 
  get; 
  set; 
}
    
[ItemRegularExpression("[a-zA-Z]*")]
public virtual IList<string> LettersOnly { 
  get; 
  set; 
}
    
[ItemStringLength(3)]
public virtual IList<string> ListOfAcronyms { 
  get; 
  set; 
}
```

Provide custom metadata and validators to the items inside the list. For example, if a `List<ContactBlock>` is defined, create a metadata extender just for `ContactBlock` or a custom validator of `ContactBlock`. The system applies it to each instance created within the list property.

## Render a list

Display templates control how list properties appear on the page.

```csharp
public virtual IList<string> UniqueSellingPoints { 
  get; 
  set; 
}
```

To use the `PropertyFor` helper method, you must define a specific Display template, and to do that, you must first attach a `UIHint` to your property:

```csharp
[UIHint("StringsCollection")]
public virtual IList<string> UniqueSellingPoints { 
  get; 
  set; 
}
```

Then add a `StringsCollection.cshtml` file to the `~/Views/Shared/DisplayTemplates` folder.

The Alloy package includes an example of a Display Template:

```csharp
@model IEnumerable<string>
  @if(Model != null && Model.Any())
    {
      <ul>
        @foreach(var stringValue in Model) {
          <li>@stringValue</li>
        }
      </ul>
    }
```

Add a `UniqueSellingPoints` property to your page template:

`@Html.PropertyFor(x => x.CurrentPage.UniqueSellingPoints)`

The on-page edit displays the following result:

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

The same approach applies to more complex types:

```csharp
public virtual IList<ContactBlock> ContactBlocks { 
  get; 
  set; 
}
```

The system looks for a partial view for `ContactBlock` inside `~/Views/Shared/DisplayTemplates` and renders the items as ul -> li by default.

Create a custom display template for the whole list if a simple ul or list is insufficient.
