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:

  1. Create ue/models/blocks/your-block.json with the proper structure
  2. Add the new block to the filters list of ue/models/blocks/section.json
  3. Run npm run build:json to regenerate the consolidated files
  4. 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

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:

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:

  1. Order matters: The order of entries in the fields array 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.

  2. 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) and div: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] and img[alt] can be used to target specific attributes of elements.
  3. CSS selectors are applied on the source HTML: the CSS selectors are applied on the HTML source delivered by .ue.da.live before 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:

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:

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:

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:

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:

Choosing the Right Block Type

When designing a new block, consider these questions:

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:

  1. 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.
  2. Open the page in Universal Editor The page should full render in UE already.
  3. 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)".
  1. Open developer console and change to the network tab
  2. Open the block properties

The properties panel will be empty as the block has no instrumentation yet.

  1. Inspect the /details call via the network tab

Inspect the call response via the "preview" tab, it should look similar to: