---
title: Edit objects
slug: cms-13/docs/edit-objects
docTags: 
createdAt: 2026-07-30T20:22:06.232Z
---

The edit objects framework generates editor views for .NET objects automatically, similar to scaffolding in MVC. Use it to build editing interfaces without writing custom UI code for each object type.

Optimizely Content Management System (CMS) extends the MVC metadata system with additional editing functionality. Define rules for object editing through class and property metadata attributes, as in the following example:

```csharp
public class Humanoid {
  [Required]
  public string Name {
    get;
    set;
  }

  [Range(0, 999)]
  public int Age {
    get;
    set;
  }

  public bool IsSuperHero {
    get;
    set;
  }
}
```

The `Humanoid` class has three properties. Two have attributes that describe validation rules for editing the object. The `Name` property is required, and the `Age` property must be between 0 and 999 to be valid.

MVC has built-in editors for primitive types and a convention-based system for placing user controls within folders to add editors for a type. CMS also has built-in editors for primitive types. When CMS generates an editor for a type, it traverses the object graph until it finds a registered editor.

For this example class, no editor is registered for the `Humanoid` type. The object editor assembler checks the properties instead. Because the three properties are primitive types with registered editors, CMS generates three property editors.

## Object editing metadata and EditorDescriptor

The metadata layer between model classes and the generated UI controls how CMS builds the editing interface. Override defaults and extend behavior through `EditorDescriptor` classes.

When CMS creates an editor for an object, it builds a metadata hierarchy by extracting metadata from the object properties and applying global metadata handlers. A global metadata handler extends or changes metadata from the classes.

The following example maps a client-side widget to a specific type through a metadata handler:

```csharp
[EditorDescriptorRegistration(TargetType = typeof(string))]
public class StringEditorDescriptor : EditorDescriptor {
  public StringEditorDescriptor() {
    ClientEditingClass = "dijit/form/ValidationTextBox";
  }
}
```

The metadata handler inherits from `EditorDescriptor`. This class has properties that apply settings to the metadata object. The base implementation sets values only when the model does not specify a setting, so model-defined settings override the generic settings in this class.

The class also has a `ModifyMetadata` method that CMS calls after extracting metadata from the class attributes. Override this method to set custom settings that the `EditorDescriptor` properties do not support and access the metadata object directly.

CMS registers an editor descriptor as a singleton instance. Settings in the constructor apply to the application life cycle. Use the `ModifyMetadata` method for dynamic settings, such as user-specific settings or translations.

## Metadata handler registry

The `MetadataHandlerRegistry` class maps types with metadata handlers and applies handlers to metadata extracted from an object. Use it to register custom metadata handlers that extend how CMS processes properties of specific types.

In the previous example, the `EditorDescriptor` class uses the `EditorDescriptorRegistration` attribute for registration. Add metadata handlers directly in an initialization module as an alternative:

```csharp
private void SetupPrimitiveTypesEditors(EPiServer.Framework.Initialization.InitializationEngine context) {
  MetadataHandlerRegistry factory = ServiceLocator.Current.GetInstance<MetadataHandlerRegistry>();
  //string
  factory.RegisterMetadataHandler(typeof(string), new StringEditorDescriptor());
    
  //DateTime
  factory.RegisterMetadataHandler(typeof(DateTime), new DateTimeEditorDescriptor());
}
```

CMS calls `StringEditorDescriptor` each time it extracts metadata for a `string` property and `DateTimeEditorDescriptor` each time it extracts a `DateTime`.

Add a metadata handler for multiple types:

```csharp
factory.RegisterMetadataHandler(new Type[] { typeof(decimal), typeof(decimal?) }, new DecimalEditorDescriptor());
```

## Metadata providers

Implement a custom `IMetadataProvider` to take over metadata generation for an entire class. For example, when editing a `PageData` class, focus on the items in the `Properties` collection that contains the data for the class.

Another use case is a third-party assembly where metadata attributes are not available. Register a metadata handler that implements the `IMetadataProvider` interface. This interface has two methods: `CreateMetadata` for the top-level object, and `GetMetadataForProperties` for the sub-properties.

The following example implements the `IMetadataProvider` interface for the `ExampleClass` class:

```csharp
public class MetadataProvider: IMetadataProvider {
  private readonly ExtensibleMetadataProvider _extensibleMetadataProvider;
  private readonly LocalizationService _localizationService;
  private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider;

  public MetadataProvider(
    ExtensibleMetadataProvider extensibleMetadataProvider,
    LocalizationService localizationService,
    IValidationAttributeAdapterProvider validationAttributeAdapterProvider) {
    _extensibleMetadataProvider = extensibleMetadataProvider;
    _localizationService = localizationService;
    _validationAttributeAdapterProvider = validationAttributeAdapterProvider;
  }

  public ExtendedMetadata CreateMetadata(IEnumerable<Attribute> attributes,
    Type containerType,
    Func<object> modelAccessor,
    Type modelType,
    string propertyName) {
    var defaultMetadata = string.IsNullOrEmpty(propertyName)
      ? _extensibleMetadataProvider.DefaultProvider.GetMetadataForType(modelType) as DefaultModelMetadata
      : _extensibleMetadataProvider.DefaultProvider.GetMetadataForProperty(containerType, propertyName) as DefaultModelMetadata;

    var metadata = new ExtendedMetadata(
      defaultMetadata,
      _validationAttributeAdapterProvider,
      _extensibleMetadataProvider,
      containerType,
      modelAccessor,
      _localizationService);

    metadata.DisplayName = "My Property Name";
    metadata.Description = () => "Foo bar2";
    return metadata;
  }

  public IEnumerable<ExtendedMetadata> GetMetadataForProperties(object container, Type containerType) {
    return new List<ExtendedMetadata>();
  }
}
```

:::hint{type="info"}
**Note**
CMS calls metadata extenders even for metadata extracted from a custom `IMetadataProvider`.
:::

## Common attributes

Reference these metadata attributes to control how the object editing system processes properties.

### .NET attributes

The following .NET attributes define validation and display behavior for properties:

| Attribute        | Property           | Effect                                                                                                                    |
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `Display`        | `ShortDisplayName` | The name displayed as a label.                                                                                            |
|                  | `Order`            | How this property is ordered compared to other properties.                                                                |
|                  | `GroupName`        | The identifier of the group that the property belongs to.                                                                 |
| `Editable`       | `AllowEdit`        | Whether the property is read-only. This attribute overrides ReadOnly when both **Editable** and **ReadOnly** are defined. |
| `ReadOnly`       | `IsReadOnly`       | Whether the property is read-only.                                                                                        |
| `Required`       | `None`             | The property becomes required.                                                                                            |
| `ScaffoldColumn` | `ShowForEdit`      | Whether the property displays when editing.                                                                               |

### Additional EPiServer attributes

The following EPiServer-specific attributes control client-side editing widgets and layout:

| Attribute       | Property               | Effect                                                                                                     |
| --------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| `ClientEditor`  | `ClientEditingClass`   | The Dojo widget class name.                                                                                |
|                 | `ClientEditingPackage` | The Dojo package required to load the widget. Required only when the package differs from the widget name. |
|                 | `DefaultValue`         | The default value of the widget.                                                                           |
|                 | `EditorConfiguration`  | Settings passed to the widget as configuration, such as `min` and `max` values for an integer.             |
|                 | `LayoutClass`          | The widget class responsible for the layout of this object and its children.                               |
| `GroupSettings` | `Name`                 | The identifier that matches **GroupName** for the property.                                                |
|                 | `Title`                | The title displayed for the group in the widget.                                                           |
|                 | `ClientLayoutClass`    | The widget class for the group.                                                                            |

## Validate with the MVC-style model

The CMS object editing framework supports MVC built-in annotation-based validators. Use validation attributes on model properties to enforce constraints on both server and client sides automatically.

The following example has a `Person` class with three properties: `Name`, `YearOfBirth`, and `Email`.

```csharp
public class Person {
  public string Name {
    get;
    set;
  }
  public int YearOfBirth {
    get;
    set;
  }
  public string Email {
    get;
    set;
  }
}
```

The editor generated by the framework validates entered data as follows:

- `Name` – One to 50 characters long and not empty.
- `YearOfBirth` – 1900 to 2010.
- `Email` – A valid email address.

In MVC, decorate the class with validation attributes:

```csharp
public class Person {
  [StringLength(50, ErrorMessage = "Name should not be longer than 50 character")]
  [Required(ErrorMessage = "Person must have some name")]
  public string Name {
    get;
    set;
  }

  [Range(1900, 2010, ErrorMessage = "The person should be born between 1900 and 2010")]
  public int YearOfBirth {
    get;
    set;
  }

  [RegularExpression(@"\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", ErrorMessage = "Invalid Email address")]
  public string Email {
    get;
    set;
  }
}
```

The object editing framework understands these validation attributes and sends validation information to the client editors. The framework maps MVC validation rules to Dojo rules, so client-side validation works without further configuration. The following widget settings apply:

- `required` – Set to `true` when the editing property has `[Required]`.
- `constraints` – The min and max constraints for range validation.
- `regEx` – Mapped directly to the pattern in `[RegularExpression]`.
- `missingMessage` – The error message from `[Required]`.
- `invalidMessage` – A combination of error messages from validation attributes other than `[Required]`, separated by newline characters (CR/LF).

For the example, the client widget initial settings look as follows:

**Name**

```xml
<div name='Name'
     data-dojoType='dijit/form/ValidationTextBox'
     data-dojoProps='required: true, regEx: "^.{0,50}$", 
     missingMessage: "Person must have some name", 
     invalidMessage: "Name should not be longer than 50 character"' />
```

**YearOfBirth**

```xml
<div name='YearOfBirth'
     data-dojoType='dijit/form/NumberSpinner'
     data-dojoProps='constraints: {min: 1900, max: 2000}, 
     invalidMessage: "The person should be born between 1900 and 2000"' />
```

**Email**

```xml
<div name='Email'
     data-dojoType='dijit/form/ValidationTextBox'
     data-dojoProps='regEx: "\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", 
     invalidMessage: "Invalid Email address"' />
```

### Limitations with multiple validation attributes

Certain attribute combinations create validation conflicts because several validation attributes use the `regEx` field for the client widget. Specifying the `regEx` attribute and constraints settings through the `EditorConfiguration` property of the `ClientEditor` attribute overrides the model validation rules. For a property like the following:

```csharp
[ClientEditor(ClientEditingClass = "dijit/form/HorizontalSlider", DefaultValue = "0",
  EditorConfiguration = "'constraints': { 'min': 0, 'max': 10 }")]
[Range(1900, 2000, ErrorMessage = "The person should be born between 1900 and 2000")]
public int YearOfBirth {
  get;
  set;
}
```

The widget does not produce the expected validation. The resulting widget is:

```xml
<div name='YearOfBirth'
     data-dojoType='dijit/form/HorizontalSlider'
     data-dojoProps='constraints: {min: 0, max: 10, 
     invalidMessage: "The person should be born between 1900 and 2000"' />
```

The `StringLength` and `RegularExpression` attributes do not work together because the framework translates both into the client `regEx` setting. The `RegularExpression` attribute takes higher priority. For example, to prevent the `Email` address from exceeding a length limit:

```csharp
[RegularExpression(@"\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", ErrorMessage = "Invalid Email address")]
[StringLength(50)]
public string Email { 
  get; 
  set; 
}
```

In this case, CMS does not process `StringLength`. Define the length constraint in the regular expression pattern instead:

```csharp
[RegularExpression(@"^(?=.{0,50}$)\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", ErrorMessage = "Invalid Email address")]
public string Email { 
  get; 
  set; 
}
```

## Validate a custom widget

Widgets that inherit `dijit.form.ValidationTextBox` support model validations by default. For a custom widget built from scratch, follow these rules to integrate with MVC model validations.

This example creates a widget for editing a money amount. The value is a ranged number followed by a currency sign like `$` or `E`. In the model class, set validation information as follows:

```csharp
[Range(0, 50)]
[RegularExpression("^\\d*[\\$,E]$")]
[ClientEditor(ClientEditingClass = "some/example/MoneyEditor")]
public int Amount { 
  get; 
  set;
}
```

Create a widget that inherits `dijit._Widget` and renders an HTML input. The startup skeleton follows:

```javascript
define(['dijit/_Widget'], function (_Widget) {
  return declare([_Widget], {
    templateString: '<input type="textbox" id="widget_${id}" dojoattachevent="onchange: _onValueChanged" />',
    //properties declaration
    //method declaration
    //event handlers
    _onValueChanged: function () {}
  });
});
```

When CMS creates the widget, the framework mixes in validation properties. Define those in the widget:

```javascript
//properties declaration        
    required       : false,
    
    missingMessage : 'Value cannot be empty',
    invalidMessage : 'Entered value is invalid',
    
    regExp         : '.*',
    constraints    : {},
```

Override the `postMixinProperties` method to verify that validation properties are set correctly:

```javascript
//method declaration
validate: function () {
  var value = dojo.byId('widget_' + this.id).value;
  var amount = value.length > 0 ? value.substring(0, value.length - 1) : null;

  if (this.required && !value) {
    //display error message using this.missingMessage
    return false;
  }

  if (this.regEx && value.test && !value.test(new RegExp(this.regEx))) {
    //display error message using this.invalidMessage
    return false;
  }

  if ((amount !== null) && (amount < this.constraints.min || amount > this.constraints.max)) {
    //display error message using this.invalidMessage
    return false;
  }

  return true;
}
```

When the containing form validates, it checks child widgets for `validate` methods. The return value indicates validity. Use the validation information properties to write validation logic:

```javascript
//event handlers
_onValueChanged: function () {
  this.validate();
}
```

Most `dijit.*` widgets support on-the-fly validation. Listen to the `onChange` event and call the `validate` method for the same behavior.
