Create Custom Property Types
Why create a custom property type
Section titled βWhy create a custom property typeβBuilt-in property types cover common scenarios β strings, numbers, content references, rich text. Sometimes your content model requires a value that does not map cleanly to any of those. A color picker, a geographic coordinate, or a structured address field all benefit from a dedicated property type. Custom property types give editors a purpose-built UI and give you type-safe access in code.
What you will do
Section titled βWhat you will doβ- Define a backing class that holds the property value
- Create a
PropertyDatasubclass that serializes and deserializes the value - Register an editor descriptor so the CMS editor renders a custom widget
- Use the property on a content type
Define the backing model
Section titled βDefine the backing modelβStart with a plain C# class that represents the structured value you want to store.
namespace MySite.Models.Properties;
public class GeoCoordinate
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public override string ToString() =>
$"{Latitude},{Longitude}";
public static GeoCoordinate Parse(string raw)
{
var parts = raw.Split(',');
return new GeoCoordinate
{
Latitude = double.Parse(parts[0]),
Longitude = double.Parse(parts[1])
};
}
} Keep the backing model serializable. The CMS stores property values as strings or JSON in the database, so your type must round-trip cleanly.
Create the PropertyData subclass
Section titled βCreate the PropertyData subclassβThe PropertyData subclass bridges your backing model and the CMS storage layer.
using Optimizely.Cms.Core.PropertyDefinitions;
namespace MySite.Models.Properties;
[PropertyDefinitionTypePlugIn(
DisplayName = "Geo Coordinate",
Description = "Latitude and longitude pair")]
public class PropertyGeoCoordinate : PropertyData
{
private GeoCoordinate? _coordinate;
public override Type PropertyValueType => typeof(GeoCoordinate);
public override object? Value
{
get => _coordinate;
set
{
SetPropertyValue(value, () =>
{
_coordinate = value switch
{
GeoCoordinate gc => gc,
string s when !string.IsNullOrEmpty(s) => GeoCoordinate.Parse(s),
_ => null
};
});
}
}
protected override string? LongString => _coordinate?.ToString();
public override void ParseToSelf(string rawValue)
{
Value = rawValue;
}
} Key points:
PropertyValueTypetells the CMS what .NET type the property exposes.LongStringcontrols how the value persists to the database.ParseToSelfhandles deserialization when content loads.
Register an editor descriptor
Section titled βRegister an editor descriptorβAn editor descriptor tells the CMS which Dojo widget to render for your property.
using Optimizely.Cms.Shell.ObjectEditing;
using Optimizely.Cms.Shell.ObjectEditing.EditorDescriptors;
namespace MySite.EditorDescriptors;
[EditorDescriptorRegistration(
TargetType = typeof(GeoCoordinate),
UIHint = "GeoCoordinate")]
public class GeoCoordinateEditorDescriptor : EditorDescriptor
{
public override void ModifyMetadata(
ExtendedMetadata metadata,
IEnumerable<Attribute> attributes)
{
ClientEditingClass =
"mysitewidgets/GeoCoordinateEditor";
base.ModifyMetadata(metadata, attributes);
}
} The ClientEditingClass path points to a Dojo module under your ClientResources folder. You build the JavaScript widget there to render latitude/longitude inputs or a map picker.
Use the property on a content type
Section titled βUse the property on a content typeβusing Optimizely.Cms.Core;
using Optimizely.Cms.Core.Attributes;
using System.ComponentModel.DataAnnotations;
namespace MySite.Models.Pages;
[ContentType(
DisplayName = "Store Location",
GUID = "a1b2c3d4-e5f6-7890-abcd-123456789abc")]
public class StoreLocationPage : PageData
{
[Display(Name = "Store Name", Order = 10)]
[Required]
public virtual string StoreName { get; set; }
[Display(Name = "Coordinates", Order = 20)]
[UIHint("GeoCoordinate")]
public virtual GeoCoordinate? Coordinates { get; set; }
} The [UIHint] value must match the one you registered in the editor descriptor.
Common issues
Section titled βCommon issuesβ| Issue | Cause | Fix |
|---|---|---|
| Property shows as plain text box | Editor descriptor not found | Verify ClientEditingClass path and UIHint match |
| Value lost on save | LongString returns null | Ensure serialization in LongString is correct |
| Type not available in admin | Missing PropertyDefinitionTypePlugIn | Add the attribute to your PropertyData class |