Enable your project for Universal Editor
Universal Editor is supported on Author Bus via Adobe's Early Access Technology program. You can store your content in Author Bus (the repository that powers da.live) while editing the content using Universal Editor.
Getting started
The best way to get started quickly is to clone the da-block-collection site. This site already has all UE instrumentation built in. To get a sense of the effort involved in adding it to your own project, inspect the ue folder of the da-block-collection repository.
Building the component definition files
The tree component definition files component-models.json , component-definitions.json , component-filters.json controlling the UE instrumentation must be stored in the project root folder. You can either edit these files directly — which is not really recommended, as they can grow quickly if you have more blocks — or use the simple bundling tool that we provide.
Instead of maintaining three massive JSON files with all our component configurations, we keep them organized in separate files per component. We recommend following the da-block-collection approach and keeping the files organised in the ue/models folder. The file structure looks like the following:
ue/models/
├── blocks/
│ ├── accordion.json # Contains definitions, models, and filters for accordion
│ ├── cards.json # Contains definitions, models, and filters for cards
│ └── ...
├── component-models.json # Template that references all block models
├── component-definition.json # Template that references all block definitions
└── component-filters.json # Template that references all block filters
This repo also contains a build:json script build pipeline that consolidates Universal Editor from ue/models into the final configuration files. Think of it like a bundler, but for JSON configuration instead of JavaScript. You can run npm run build:json to regenerate the consolidated files in the repo's root folder. Additionally, the sample repository also uses husky to install a pre-commit hook. This is optional, but it helps you to bundle the UE JSON files with every commit.
This approach keeps our codebase organised while ensuring Universal Editor gets the configuration format it expects.
When you add a new component:
- Create
ue/models/blocks/your-block.jsonwith the proper structure - Add the new block to the
filterslist ofue/models/blocks/section.json - Run
npm run build:jsonto regenerate the consolidated files - The new component will automatically be included in all three output files
How is a block .json file structured?
Lets review https://github.com/aemsites/da-block-collection/blob/main/ue/models/blocks/accordion.json for example.
Each file has three main objects:
- definitions
- models
- filters
definitions
Here we define the block itself, the name under which it will be displayed in UE, and how the table stored in the document will look.
The da plugin is used for this. There are two options for specifying the block table:
-
name + rows + columns:
namedefines the block namerows&columnsare number values defining how many rows and how many columns a new empty block will haverows&columnsare optional, if omitted they will default to 1
-
fields: blocks usually have one or multiple fields, these are defined via the
fieldsarray that maps field names to CSS selectors in the form of{ "name": "text", "selector": "div:nth-child(2)"}. Each field used in the models definition must have an entry in thefieldsarray. This allows UE to understand which table cell structure corresponds to which field in the properties panel. -
unsafeHTML: can be used to define a block including some dummy content or placeholders which are added to the document when the block is added to UE.
rows + columns or unsafeHTML are used exclusively; only one of the two may be defined.
In the case of accordion, two definitions were created: one for the block itself and a second one for the accordion entries, as the block can contain several similar accordion entries. The accordion-item definition includes a fields array that maps the "summary" field to the first div and the "text" field to the second div using CSS selectors.
Important notes on the fields array:
-
Order matters: The order of entries in the
fieldsarray is important as it determines the sequence in which empty cells are created when a new block is added in UE. Fields will be populated in the order they appear in the array. -
CSS selector structure: The selectors use
div:nth-child(x)to address specific rows and columns of the block table:- For multi-row/column blocks: Use
div:nth-child(n)to target specific cells. For example, in a card with 1 row and 2 columns,div:nth-child(1)targets the first column (image) anddiv:nth-child(2)targets the second column (text). - For single-row/column blocks: Use nested selectors like
div>div>picture>img[src]to target elements within the single cell. - Attribute selectors like
img[src]andimg[alt]can be used to target specific attributes of elements.
- For multi-row/column blocks: Use
-
CSS selectors are applied on the source HTML: the CSS selectors are applied on the HTML source delivered by
.ue.da.livebefore the client-side project JavaScript is running
models
The fields for the side panel in the Universal Editor are defined using the field list in models. See the UE documentation for more information.
The name attribute in the model must match the field names defined in the fields array of the corresponding definition. This creates the connection between the UE properties panel and the actual content cells in the block table. The CSS selectors specified in the definition's fields array determine which table cell each model field will edit. A detailed step-by-step flow on how to get the correct CSS selectors is documented below.
filters
Filters are only required for more complex blocks. They are used to define the relationship between blocks and items for a block. This allows UE users to add further accordion entries for an accordion, for example, using the + button in UE.
Simple blocks do not require a filter and can only define an empty array here.
Block Types
When creating blocks for Universal Editor, you'll work with three main block types. Each type has a different structure and use case, and understanding when to use each type is essential for building effective content models.
Simple Blocks
Simple blocks are the most straightforward block type. They render each property in a single row/column in the order defined in the model. Each field maps to a cell in the block table, creating a linear structure.
Use cases:
- Single-purpose blocks with no nested items (e.g., hero, quote, call-to-action)
- Blocks where authors need to fill in a fixed set of fields
- Blocks that don't need to contain multiple similar items
Example: Hero Block
A hero block typically contains an image, heading, and text. See the hero.json reference for a complete example.
- definitions (json)
- models (json)
"definitions": [
{
"title": "Hero",
"id": "hero",
"model": "hero",
"plugins": {
"da": {
"name": "hero",
"rows": 1,
"columns": 1
"fields": [
{
"name": "image",
"selector": "div>div>picture>img[src]"
},
{
"name": "imageAlt",
"selector": "div>div>picture>img[alt]"
},
{
"name": "text",
"selector": "div>div>h1"
}
]
}
}
}
],
"models": [
{
"id": "hero",
"fields": [
{
"component": "reference",
"valueType": "string",
"name": "image",
"label": "Image",
"multi": false
},
{
"component": "text",
"valueType": "string",
"name": "imageAlt",
"label": "Alt",
},
{
"component": "text",
"name": "text",
"label": "Text",
"valueType": "string"
}
]
}
],
This creates a simple table structure where each field appears in its own row:
- hero (markdown)
+------------------+
| Hero |
+==================+
| [Image] |
| # Heading |
| Paragraph text |
+------------------+
Container Blocks
Container blocks allow adding multiple child items of the same type, creating a two-dimensional structure. The block can have its own properties (rendered as rows with a single column), plus a list of repeatable child items where each item becomes a row with properties as columns.
Use cases:
- Lists of similar items (e.g., accordion, cards, tabs)
- Blocks where authors need to add/remove multiple entries
- Collections that maintain consistent structure across items
Example: Accordion Block
An accordion contains multiple accordion items, each with its own content. See the accordion.json reference for a complete example.
- definitions (json)
- models (json)
- filters (json)
"definitions": [
{
"title": "Accordion",
"id": "accordion",
"model": "accordion",
"filter": "accordion",
"plugins": {
"da": {
"name": "accordion",
"rows": 1,
"columns": 2
}
}
},
{
"title": "Accordion Item",
"id": "accordion-item",
"model": "accordion-item",
"plugins": {
"da": {
"name": "accordion-item",
"rows": 2,
"columns": 0,
"fields": [
{ "name": "summary", "selector": "div:nth-child(1)"},
{ "name": "text", "selector": "div:nth-child(2)"}
]
}
}
}
],
"models": [
{
"id": "accordion-item",
"fields": [
{
"component": "richtext",
"valueType": "string",
"name": "summary",
"value": "",
"label": "Summary",
"required": true
},
{
"component": "richtext",
"name": "text",
"value": "",
"label": "Text",
"valueType": "string",
"required": true
}
]
}
],
"filters": [
{
"id": "accordion",
"components": [
"accordion-item"
]
}
]
This creates a table where child items can be added dynamically:
- accordion (markdown)
+-------------------------+-------------------------+
| Accordion | |
+=========================+=========================+
| First item summary | First item text |
+-------------------------+-------------------------+
| Second item summary | Second item text |
+-------------------------+-------------------------+
Key characteristics:
- Requires both a parent definition (the container) and a child definition (the item)
- The parent's
filterproperty references a filter that specifies which child components can be added - The filter's
componentsarray lists the allowed child component IDs - Authors can use the + button in UE to add new items
Key-Value Blocks
Key-value blocks render properties as a two-column table where the first column shows the property name and the second column shows the value. This format is useful for configuration-style blocks that are read as key-value pairs.
Use cases:
- Configuration blocks (e.g., section metadata, block metadata)
- Blocks that need to be read as key-value pairs by JavaScript
- Settings or parameters that benefit from explicit labeling
Example: Product Recommendations Block
A configuration block that stores parameters for fetching product recommendations. See the product-recommendations reference for a complete example.
- definitions (json)
- models (json)
"definitions": [
{
"title": "Product Recommendations",
"id": "product-recommendations",
"model": "product-recommendations",
"plugins": {
"da": {
"name": "product-recommendations",
"type": "key-value-block"
}
}
}
],
"models": [
{
"id": "product-recommendations",
"fields": [
{
"component": "text",
"name": "type",
"label": "Recommendation Type"
},
{
"component": "number",
"name": "limit",
"label": "Number of Products"
},
{
"component": "text",
"name": "source",
"label": "Data Source"
}
]
}
],
This creates a key-value table structure:
- accordion (markdown)
+-----------------------------+----------------------+
| Product Recommendations | |
+=============================+======================+
| type | similar-products |
+-----------------------------+----------------------+
| limit | 4 |
+-----------------------------+----------------------+
| source | /products.json |
+-----------------------------+----------------------+
Key characteristics:
- Uses
"type": "key-value-block"in thedaplugin to create the key value two-column layout - Field names appear as labels in the first column
- Field values appear in the second column
- Often used with plain text or number fields rather than rich text
- JavaScript code typically reads these blocks by iterating over rows and treating them as key-value pairs
Choosing the Right Block Type
When designing a new block, consider these questions:
- Does the block need multiple similar items? → Use a container block
- Will JavaScript read this as configuration? → Use a key-value block
- Is it a single, fixed set of fields? → Use a simple block
You can also combine approaches. For example, a container block could have key-value properties at the block level, with simple or structured child items.
Instrumenting custom blocks
Additional UE instrumentation might be needed for you custom blocks. It is recommended to do this in a branch of your project. This is how you can easily add them:
- Add the block for a (test) page. This can be done with the document editor. If the block supports different format or block options it is recommended to add each version.
- Open the page in Universal Editor The page should full render in UE already.
- Navigate to the content tree in UE and select the block The overall site structure is automatically detected by UE. This includes all the block. Blocks in your project without UE instrumentation will be shown as " (no definition)".
- Open developer console and change to the network tab
- Open the block properties
The properties panel will be empty as the block has no instrumentation yet.
- Inspect the /details call via the network tab
Inspect the call response via the "preview" tab, it should look similar to: