# Custom property editors support

Out of the box, the Delivery API supports custom property editors, ensuring they are rendered alongside the built-in ones in Umbraco. However, if the output generated by your property editor isn't optimal for a headless context, you have the ability to customize the API response. This customization won't impact the Razor rendering, allowing you to tailor the Content Delivery API response according to your specific requirements.

In this article, we'll look into how you can work with the `IDeliveryApiPropertyValueConverter` interface and implement custom [property expansion](/umbraco-cms/13.latest/reference/content-delivery-api.md#property-expansion) for your custom property editors.

## Prerequisite

The examples in this article revolve around the fictional `My.Custom.Picker` property editor. This property editor stores the key of a single content item and is backed by a property value converter.

We will not dive into the details of creating a custom property editor for Umbraco in this article. If you need guidance on that, refer to the [Creating a Property Editor](https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-property-editor) and [Property Value Converters](https://github.com/umbraco/UmbracoDocs/blob/main/13/umbraco-cms/extending/property-editors/property-value-converters/README.md) articles.

## Implementation

To customize the output of a property value editor in the Delivery API, we need to opt-in by implementing the `IDeliveryApiPropertyValueConverter` interface.

The code example below showcases the implementation of this interface in the property value converter for `My.Custom.Picker`. Our focus will be on the methods provided by the `IDeliveryApiPropertyValueConverter`, as they are responsible for customizing the Delivery API response.

Towards the end of the example, you will find the response models that we will be using.

The `IsConverter()` and `GetPropertyValueType()` methods are inherited from the `PropertyValueConverterBase` class, which is covered in the [Property Value Converters](https://github.com/umbraco/UmbracoDocs/blob/main/13/umbraco-cms/extending/property-editors/property-value-converters/README.md) article.

{% code title="MyCustomPickerValueConverter.cs" lineNumbers="true" %}

```csharp
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
using Umbraco.Cms.Core.PublishedCache;

namespace Umbraco.Docs.Samples;

public class MyCustomPickerValueConverter : PropertyValueConverterBase, IDeliveryApiPropertyValueConverter
{
    private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
    private readonly IApiContentRouteBuilder _apiContentRouteBuilder;

    public MyCustomPickerValueConverter(
        IPublishedSnapshotAccessor publishedSnapshotAccessor,
        IApiContentRouteBuilder apiContentRouteBuilder)
    {
        _publishedSnapshotAccessor = publishedSnapshotAccessor;
        _apiContentRouteBuilder = apiContentRouteBuilder;
    }

    public override bool IsConverter(IPublishedPropertyType propertyType)
        => propertyType.EditorAlias.Equals("My.Custom.Picker");

    public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
        => typeof(Guid?);

    public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType)
        => PropertyCacheLevel.Elements;

    public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType)
        => PropertyCacheLevel.Snapshot;

    public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType)
        => typeof(DeliveryApiCustomPicker);

    public object? ConvertIntermediateToDeliveryApiObject(
        IPublishedElement owner,
        IPublishedPropertyType propertyType,
        PropertyCacheLevel referenceCacheLevel,
        object? inter,
        bool preview,
        bool expanding)
    {
        if (inter is null)
        {
            return null;
        }

        return BuildDeliveryApiCustomPicker(inter, expanding);
    }

    private DeliveryApiCustomPicker? BuildDeliveryApiCustomPicker(object inter, bool expanding)
    {
        if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) ||
            publishedSnapshot?.Content is null)
        {
            return null;
        }

        if (!Guid.TryParse(inter as string, out Guid id))
        {
            return null;
        }

        return new DeliveryApiCustomPicker { Id = id };
    }
}

public class DeliveryApiCustomPicker
{
    public Guid Id { get; set; }

    public DeliveryApiItemDetails? ItemDetails { get; set; }
}

public class DeliveryApiItemDetails
{
    public string? Name { get; set; }

    public IApiContentRoute? Route { get; set; }
}
```

{% endcode %}

The Implementation of the `IDeliveryApiPropertyValueConverter` interface can be found in the following methods:

* `GetDeliveryApiPropertyCacheLevel()`: This method specifies the cache level used for our property representation in the Delivery API response.
* `GetDeliveryApiPropertyCacheLevelForExpansion()`: This method specifies the cache level used for our property representation in the Delivery API response when [property expansion](/umbraco-cms/13.latest/reference/content-delivery-api/property-expansion-and-limiting.md) is applied.
* `GetDeliveryApiPropertyValueType()`: This method defines the value type of the custom property output for the Delivery API response.
* `ConvertIntermediateToDeliveryApiObject()`: This method converts the value from the property editor to the desired custom object in a headless context.

In the given example, the content key (`Guid` value) is used when rendering with Razor. This is sufficient because Razor provides full access to the published content within the rendering context. In a headless context, we do not have the same access. To prevent subsequent round-trips to the server, we create a richer output model specifically for the Delivery API.

The following example request shows how our custom implementation is reflected in the resulting API response. In this case, our custom property editor is configured under the alias `"pickedItem"`.

**Request**

```http
GET /umbraco/delivery/api/v2/content/item/blog
```

**Response**

```json
{
    "name": "Blog",
    "createDate": "2023-05-25T11:26:52.591927",
    "updateDate": "2023-06-07T12:53:41.339963",
    "route": {
        "path": "/",
        "startItem": {
            "id": "88694917-ac4a-4cfa-aa82-d89a81940126",
            "path": "blog"
        }
    },
    "id": "88694917-ac4a-4cfa-aa82-d89a81940126",
    "contentType": "blog",
    "properties": {
        "pickedItem": {
            "id": "61df413c-02b2-480d-8cb5-8e3c35132838",
            "itemDetails": null
        }
    },
    "cultures": {}
}
```

## Property expansion support

Property expansion allows us to conditionally add another level of detail to the Delivery API output. Usually, these additional details are "expensive" to retrieve (for example, requiring database access to populate). By applying property expansion, we provide the option for the caller of the API to opt-in explicitly to this "expensive" operation. From the caller's perspective, the alternative might be an even more expensive additional round-trip to the server.

In our example, property expansion is implemented within `ConvertIntermediateToDeliveryApiObject()`. By considering the value of the `expanding` parameter, we can modify the `BuildDeliveryApiCustomPicker()` method as follows:

```csharp
private DeliveryApiCustomPicker? BuildDeliveryApiCustomPicker(object inter, bool expanding)
{
    if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) ||
        publishedSnapshot?.Content is null)
    {
        return null;
    }

    if (!Guid.TryParse(inter as string, out Guid id))
    {
        return null;
    }

    // Property expansion support
    if (expanding == false)
    {
        return new DeliveryApiCustomPicker { Id = id };
    }

    IPublishedContent? content = publishedSnapshot.Content.GetById(id);
    if (content is null)
    {
        return new DeliveryApiCustomPicker { Id = id };
    }

    IApiContentRoute? route = _apiContentRouteBuilder.Build(content);
    var itemDetails = new DeliveryApiItemDetails { Name = content.Name, Route = route };

    return new DeliveryApiCustomPicker { Id = id, ItemDetails = itemDetails };
}
```

If the `expanding` parameter is `false`, the method returns the same shallow representation of the referenced content item as before. Otherwise, we retrieve the corresponding `IPublishedContent` and construct our response object accordingly.

To see the expanded output in the API response, we need to add the `expand` query parameter to our request. We can use either `?expand=properties[$all]` to expand all properties or `?expand=properties[pickedItem]` to expand the specific `'pickedItem'` property.

**Request**

```http
GET /umbraco/delivery/api/v2/content/item/blog?expand=properties[pickedItem]
```

**Response**

```json
{
    "name": "Blog",
    "createDate": "2023-05-25T11:26:52.591927",
    "updateDate": "2023-06-07T12:53:41.339963",
    "route": {
        "path": "/",
        "startItem": {
            "id": "88694917-ac4a-4cfa-aa82-d89a81940126",
            "path": "blog"
        }
    },
    "id": "88694917-ac4a-4cfa-aa82-d89a81940126",
    "contentType": "blog",
    "properties": {
        "pickedItem": {
            "id": "61df413c-02b2-480d-8cb5-8e3c35132838",
            "itemDetails": {
                "name": "Codegarden",
                "createDate": "2023-05-21T00:34:11.513868",
                "updateDate": "2023-06-06T17:46:12.833186",
                "route": {
                    "path": "/codegarden/",
                    "startItem": {
                        "id": "88694917-ac4a-4cfa-aa82-d89a81940126",
                        "path": "blog"
                    }
                }
            }
        }
    },
    "cultures": {}
}
```

The `itemDetails` property of the `pickedItem` in the JSON response contains the additional details of the selected content item.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.umbraco.com/umbraco-cms/13.latest/reference/content-delivery-api/custom-property-editors-support.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
