Creating a Property Editor
A guide to creating a property editor in Umbraco
This page is a work in progress. It will be updated as the software evolves.
This guide explains how to set up a property editor and hook it into Umbraco's Data Types. It also covers the creation of a basic property editor and how we can test our property editor.
The steps we will go through in part 1 are:
This tutorial uses Typescript and Lit with Umbraco, so it does not cover Typescript or Lit. It is expected that your package is already set up to use Typescript and Lit. To read about setting up an extension in Umbraco using Typescript and Lit, please read the article Creating your first extension.
For resources on Typescript or Lit, you can find some here:
There are a lot of parallels with Creating a Custom Dashboard. The tutorial Creating a Custom Dashboard is worth a read too.
At the tutorial's end, we'll have a Umbraco Suggestions Data Type, registered in the backoffice, and assigned to a Document Type. This Data Type can create and suggest values.
Assuming you have read the tutorial Creating your first extension, you should have a folder named App_Plugins in your project. Let's call our project Suggestions. Start by creating a folder in App_Plugins called
Suggestions
.Now create the manifest file named
umbraco-package.json
at the root of the Suggestions
folder. Here we define and configure our dashboard.Add the following code
{
"$schema": "../../umbraco-package-schema.json",
"name": "My.AwesomePackage",
"version": "0.1.0",
"extensions": [
{
"type": "propertyEditorUi",
"alias": "My.PropertyEditorUi.Suggestions",
"name": "My Suggestions Property Editor UI",
"js": "/App_Plugins/Suggestions/dist/my-suggestions-property-editor-ui.element.js",
"elementName": "my-suggestions-property-editor-ui",
"meta": {
"label": "Suggestions",
"icon": "umb:list",
"group": "common",
"propertyEditorSchemaAlias": "Umbraco.TextBox"
}
}
]
}
Make sure to restart the application after you create and update
umbraco-package.json
Let's start with creating a folder
src
in our Suggestions folder. We want to start creating the web component we need for our property editor. Create a file in the src
folder with the name suggestions-property-editor-ui.element.ts
In this new file, we will add the following code:
import { LitElement, html, customElement, property } from "@umbraco-cms/backoffice/external/lit";
import { type UmbPropertyEditorExtensionElement } from "@umbraco-cms/backoffice/extension-registry";
@customElement("my-suggestions-property-editor-ui")
export class MySuggestionsPropertyEditorUIElement
extends LitElement
implements UmbPropertyEditorExtensionElement
{
@property({ type: String })
public value = "";
render() {
return html`I'm a property editor!`;
}
}
declare global {
interface HTMLElementTagNameMap {
"my-suggestions-property-editor-ui": MySuggestionsPropertyEditorUIElement;
}
}
Now our basic parts of the editor are done, namely:
- The package manifest, telling Umbraco what to load
- The web component for the editor
We will now restart our application. In the Document Type, let's add our newly added property editor "Suggestions" and save it.

We can now edit the assigned property's value with our new property editor.
We should now have a property editor that looks like this:

Let's start by creating an input field and some buttons that we can style and hook up to events. In the
suggestions-property-editor-ui.element.ts
file, update the render method to include some input fields and buttons:The Umbraco UI library is already a part of the backoffice, which means we can start using it
render() {
return html`
<uui-input
id="suggestion-input"
class="element"
label="text input"
.value=${this.value || ""}
>
</uui-input>
<div id="wrapper">
<uui-button
id="suggestion-button"
class="element"
look="primary"
label="give me suggestions"
>
Give me suggestions!
</uui-button>
<uui-button
id="suggestion-trimmer"
class="element"
look="outline"
label="Trim text"
>
Trim text
</uui-button>
</div>
`;
}
Next, let's add a little bit of styling. Update the import from lit to include CSS:
import { LitElement, html, css } from "lit";
Then add the CSS:
render() {
...
}
static styles = [
css`
#wrapper {
margin-top: 10px;
display: flex;
gap: 10px;
}
.element {
width: 100%;
}
`,
];
It should now look something like this:

It's starting to look good! Next, let's look into setting up the event logic.
Let's start with the input field. When we type something in the input field, we want the property editor's value to change to the input field's current value. We then have to dispatch an
property-value-change
event. #onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.#dispatchChangeEvent();
}
#dispatchChangeEvent() {
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {
return html`
<uui-input
id="suggestion-input"
class="element"
label="text input"
.value=${this.value || ""}
@input=${this.#onInput}
>
</uui-input>
....
}
Let's look at the suggestions button next. When we press the suggestion button we want the text to update to the suggestion that we get. Similar to how the value of our property editor changes when we write in the input field. We also want the value to change when we press the suggestion button.
First, update the import for Lit and add some suggestions to the property editor:
import { customElement, property, state } from "@umbraco-cms/backoffice/external/lit";
@property({ type: String })
public value = "";
@state()
private _suggestions = [
'You should take a break',
'I suggest that you visit the Eiffel Tower',
'How about starting a book club today or this week?',
'Are you hungry?',
];
Then update the suggestion button in the render method to call a
onSuggestion
method when we press the button #onSuggestion() {
const randomIndex = (this._suggestions.length * Math.random()) | 0;
this.value = this._suggestions[randomIndex];
this.#dispatchChangeEvent();
}
render() {
return html`
...
<uui-button
id="suggestion-button"
class="element"
look="primary"
label="give me suggestions"
@click=${this.#onSuggestion}
>
Give me suggestions!
</uui-button>
...
`;
}
The
suggestions-property-editor-ui.element.ts
file should now look something like this:import { LitElement, css, html, customElement, property, state } from "@umbraco-cms/backoffice/external/lit";
import { UmbPropertyEditorExtensionElement } from "@umbraco-cms/backoffice/extension-registry";
@customElement("my-suggestions-property-editor-ui")
export class MySuggestionsPropertyEditorUIElement
extends LitElement
implements UmbPropertyEditorExtensionElement
{
@property({ type: String })
public value = "";
@state()
private _suggestions = [
"You should take a break",
"I suggest that you visit the Eiffel Tower",
"How about starting a book club today or this week?",
"Are you hungry?",
];
#onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.#dispatchChangeEvent();
}
#onSuggestion() {
const randomIndex = (this._suggestions.length * Math.random()) | 0;
this.value = this._suggestions[randomIndex];
this.#dispatchChangeEvent();
}
#dispatchChangeEvent() {
this.dispatchEvent(new CustomEvent("property-value-change"));
}
render() {
return html`
<uui-input
id="suggestion-input"
class="element"
label="text input"
.value=${this.value || ""}
@input=${this.#onInput}
>
</uui-input>
<div id="wrapper">
<uui-button
id="suggestion-button"
class="element"
look="primary"
label="give me suggestions"
@click=${this.#onSuggestion}
>
Give me suggestions!
</uui-button>
<uui-button
id="suggestion-trimmer"
class="element"
look="outline"
label="Trim text"
>
Trim text
</uui-button>
</div>
`;
}
static styles = [
css`
#wrapper {
margin-top: 10px;
display: flex;
gap: 10px;
}
.element {
width: 100%;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"my-suggestions-property-editor-ui": MySuggestionsPropertyEditorUIElement;
}
}
Next, clear the cache, reload the document, and see the Suggestions Data Type running.

When we save or publish, the value of the Data Type is now automatically synced to the current content object and sent to the server.