---
title: Convert from IContent to ContentApiModel
slug: cms-12/api/customizing-conversion-from-icontent-to-contentapimodel
docTags: 
createdAt: 2026-08-07T16:45:43.000Z
---

This topic describes how to customize conversion from `IContent` to `ContentApiModel`. The following phases simplify the process.

- Convert from `IContent` to `ContentApiModel`.
- Filter data before and after the conversion. See how to [customize data returned to clients](docId\:G0-aJ6fe4B2u8u7UHsx1L).
- Serialize `ContentApiModel` and returns data to clients.

The conversion is responsible for converting `PropertyData` to `PropertyModel`. The following image shows the flow.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/MobyuVkhgX7ceoMi-wGJa-20260722-175336.png "imagetav4m.png")

Several important interfaces and classes appear in the flow image.

- `IContentConverterProvider` resolves the correct `IContentConverter` responsible for converting `IContent` to `ContentApiModel`. The default implementation of `IContentConvert` is `DefaultContentConverter`.
- `IPropertyConverterProvider` resolves the correct `IPropertyConverter` that takes responsibility to convert a `PropertyData` to a `PropertyModel`.

:::hint{type="warning"}
**Caution**
Developers can use the classes mentioned above, but only if you really need to because of being an internal namespace or marked as preview.
:::

Several scenarios regarding conversion that developers may face are as follows:

1. Developers would replace a default property model for property data with their model.
2. Developers create a property data type and need to associate it with a property model.
3. Developers need to customize Optimizely's conversion logic.

(1) and (2) are much safer than (3), and the following examples show how to handle those scenarios.

:::hint{type="warning"}
**Caution**
(3) is complicated and risky so do this only in urgent cases. It is not recommended. Developers can create a new implementation of `IContentConverterProvider` with a `SortOrder` higher than 100 and then, implement a new `IContentConverter` to handle conversion.
:::

## Example: customize PropertyDateTime converter

Given that a site requires a DateTime in Utc format, and you decide to create a property data type `CustomPropertyDate` to handle this, do the following steps:

- Create a `CustomPropertyDateTime`

```csharp
[Serializable]
[PropertyDefinitionTypePlugIn]
public class CustomPropertyDateTime : PropertyDate
{
    // Empty constructor should be present, otherwise it will cause exception during startup.
    public CustomPropertyDateTime()
    {
      // we should use Field, but for demonstration purpose, we use Date property
      Date = DateTime.MinValue;
    }

    public CustomPropertyDateTime(DateTime dateTime)
    {
      Date = dateTime.ToUniversalTime();
    }

    public override object Value
    {
      get => Date;
      set
      {
        switch (value)
        {
          case string s:
            Date = DateTime.Parse(s, CultureInfo.CurrentCulture).ToUniversalTime();
            Modified();
            return;
          case DateTime d:
            Date = d.ToUniversalTime();
            Modified();
            return;
            default:
            throw new NotSupportedException("Cannot parse date time format.");
        }
      }
    }

    public override PropertyDataType Type => PropertyDataType.Date;
    public override Type PropertyValueType => typeof(DateTime);
}
```

- Create a `CustomDateTimePropertyModel`

```csharp
public class CustomDateTimePropertyModel : PropertyModel<DateTime?, CustomPropertyDateTime>
  {
    public CustomDateTimePropertyModel(CustomPropertyDateTime utcDateTime) : base(utcDateTime)
    {
      Value = (DateTime)utcDateTime.Value;
    }
  }
```

- Create a `CustomPropertyConverter`

```csharp
public class CustomPropertyConverter : IPropertyConverter
{
  public IPropertyModel Convert(PropertyData propertyData, ConverterContext contentMappingContext)
  {
    return new CustomDateTimePropertyModel(new CustomPropertyDateTime((propertyData as PropertyDate).Date.Value));
  }
}
```

- Create `CustomPropertyConverterProvider`

```csharp
public class CustomPropertyConverterProvider : IPropertyConverterProvider
{
  public int SortOrder => 200;

  public IPropertyConverter Resolve(PropertyData propertyData)
  {
    if (propertyData is PropertyDate || propertyData is CustomPropertyDateTime)
    {
      return new CustomPropertyConverter();
    }

    return null;
  }
}
```

- Register at Startup in `ConfigureServices`

```csharp
services.AddSingleton<IPropertyConverterProvider, CustomPropertyConverterProvider>();
services.AddSingleton<IPropertyConverter, CustomPropertyConverter>();
```

:::hint{type="info"}
**Note**
A provider pattern is applied so that if you set `SortOrder` of another provider higher than Optimizely's, it would be used first. But if another provider does not handle the case (that is, return null as result), then Optimizely providers are used.
:::

## Example: expand multiple levels of nested ContentArea property

By default, Content Delivery API only expands one level in the response. So if you have a nested ContentArea property (such as a `ContentArea` property that contains a block that contains its own `ContentArea` property), Content Delivery API does not expand the nested `ContentArea` property. In some scenarios, you might want to return the nested `ContentArea` in one request, which you can do by creating a `ContentArea` property converter.

```csharp
[ServiceConfiguration(typeof (IPropertyConverter), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomContentAreaPropertyConverter: IPropertyConverter {
  public IPropertyModel Convert(PropertyData propertyData, ConverterContext converterContext) {
    if (propertyData is not PropertyContentArea propertyContentArea) {
      throw new InvalidOperationException();
    }

    var model = new ExpandedContentAreaPropertyModel(propertyContentArea, converterContext, ServiceLocator.Current.GetInstance<IContentExpander>());
    model.Expand(converterContext.Language);

    return model;
  }
}
```

```csharp
[ServiceConfiguration(typeof (IPropertyConverterProvider), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomPropertyConverterProvider: IPropertyConverterProvider {
  public int SortOrder => 200;

  public IPropertyConverter Resolve(PropertyData propertyData) {
    if (propertyData is PropertyContentArea) {
      return new CustomContentAreaPropertyConverter();
    }

    return null;
  }
}
```

```csharp
public class ExpandedContentAreaPropertyModel: ContentAreaPropertyModel {
  private readonly IContentExpander _contentExpander;

  public ExpandedContentAreaPropertyModel(PropertyContentArea propertyContentArea, ConverterContext converterContext, IContentExpander contentExpander): base(propertyContentArea, converterContext) {
    _contentExpander = contentExpander;
  }

  /// <summary>
  /// Herre we override Expand behaviour of this model and expands all level
  /// </summary>
  /// <param name="language"></param>
  /// <returns></returns>
  protected override IEnumerable<ContentApiModel> ExtractExpandedValue(CultureInfo language) {
    var expandedValue = new List<ContentApiModel>();
    foreach(var item in Value) {
      var expandedModel = _contentExpander.Expand(item.ContentLink, CreateExpandableContext(item.ContentLink, ConverterContext, language));

      expandedValue.Add(expandedModel);
    }

    return expandedValue;
  }

  private static ConverterContext CreateExpandableContext(ContentModelReference contentLink, ConverterContext converterContext, CultureInfo language) => new(new ContentReference(contentLink.Id.Value, contentLink.WorkId.Value), language, converterContext.Options, converterContext.ContextMode, null, "*", false);
}
```
