Skip to content

Build Custom Editor Components

⏱ 30 minutes advanced

The default CMS property editors handle text, numbers, dates, and references well. When editors need a richer interaction — a star rating picker, a tag selector, or a color palette — you build a custom editor component. A good custom editor reduces mistakes and speeds up content entry.

  1. Create a Dojo widget under ClientResources
  2. Register the widget with an editor descriptor in C#
  3. Wire the descriptor to a content property via UIHint
  4. Test the widget in the CMS editing interface

Place your widget in the ClientResources/Scripts folder of your project. The CMS editor loads Dojo modules from this path.

A star rating editor widget
javascript
define([
    "dojo/_base/declare",
    "dijit/_Widget",
    "dijit/_TemplatedMixin",
    "epi/shell/widget/_ValueRequiredMixin"
], function (declare, _Widget, _TemplatedMixin, _ValueRequiredMixin) {
    return declare(
        "mysitewidgets.StarRatingEditor",
        [_Widget, _TemplatedMixin, _ValueRequiredMixin],
        {
            templateString:
                '<div class="star-rating">' +
                '  <span data-dojo-attach-event="onclick:_select" data-value="1">&#9733;</span>' +
                '  <span data-dojo-attach-event="onclick:_select" data-value="2">&#9733;</span>' +
                '  <span data-dojo-attach-event="onclick:_select" data-value="3">&#9733;</span>' +
                '  <span data-dojo-attach-event="onclick:_select" data-value="4">&#9733;</span>' +
                '  <span data-dojo-attach-event="onclick:_select" data-value="5">&#9733;</span>' +
                '</div>',

            value: null,

            _select: function (evt) {
                var rating = parseInt(evt.target.getAttribute("data-value"), 10);
                this._set("value", rating);
                this.onChange(rating);
            },

            _setValueAttr: function (val) {
                this._set("value", val);
            }
        }
    );
});

Key details:

  • Extend dijit/_Widget and dijit/_TemplatedMixin for lifecycle management.
  • Mix in _ValueRequiredMixin so the CMS validation pipeline recognizes your widget.
  • Call this.onChange(value) whenever the user changes the value — this notifies the CMS to mark content as dirty.

Add your module path to module.config so the CMS can resolve your widget.

module.config
xml
<?xml version="1.0" encoding="utf-8"?>
<module>
  <dojo>
    <paths>
      <add name="mysitewidgets" path="Scripts" />
    </paths>
  </dojo>
</module>

Place this file in the root of your ClientResources folder. The name attribute must match the module prefix used in your define call.

The editor descriptor connects your widget to a .NET property type or UIHint.

Editor descriptor in C#
csharp
using Optimizely.Cms.Shell.ObjectEditing;
using Optimizely.Cms.Shell.ObjectEditing.EditorDescriptors;

namespace MySite.EditorDescriptors;

[EditorDescriptorRegistration(
    TargetType = typeof(int?),
    UIHint = "StarRating")]
public class StarRatingEditorDescriptor : EditorDescriptor
{
    public override void ModifyMetadata(
        ExtendedMetadata metadata,
        IEnumerable<Attribute> attributes)
    {
        ClientEditingClass = "mysitewidgets/StarRatingEditor";
        base.ModifyMetadata(metadata, attributes);
    }
}
Content type using the custom editor
csharp
using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Attributes;
using System.ComponentModel.DataAnnotations;

namespace MySite.Models.Pages;

[ContentType(
    DisplayName = "Review Page",
    GUID = "b2c3d4e5-f6a7-8901-bcde-234567890abc")]
public class ReviewPage : PageData
{
    [Display(Name = "Review Title", Order = 10)]
    [Required]
    public virtual string ReviewTitle { get; set; }

    [Display(Name = "Rating", Order = 20)]
    [UIHint("StarRating")]
    [Range(1, 5)]
    public virtual int? Rating { get; set; }
}

Build the project, navigate to the editor, and create a new Review Page. You should see the star rating widget instead of a plain number input.

Place a CSS file alongside your widget and reference it in module.config.

Widget styles
css
.star-rating span {
    font-size: 24px;
    cursor: pointer;
    color: #ccc;
    transition: color 0.15s;
}
.star-rating span:hover,
.star-rating span:hover ~ span {
    color: #f5a623;
}
IssueCauseFix
Widget not loadingModule path not registeredVerify module.config path and name
Value not savingonChange not calledCall this.onChange(value) on every change
Widget renders but is empty_setValueAttr missingImplement the setter to handle initial value