Skip to main content

Universal Editor

The Universal Editor (UE) is Adobe's framework-agnostic visual editor. For EDS, it replaces the classic AEM Page Editor and gives authors a true WYSIWYG experience on top of the same block-based pipeline that powers document-based authoring.

This chapter is about the block-side wiring -- the three JSON config files plus the HTML attributes that tell UE what to make editable. Authoring concepts and when-to-pick-UE-vs-docs live in Authoring models.

The three JSON files

Three files live at the root of an EDS project that supports UE:

FileWhat it controls
component-definition.jsonWhich blocks exist and the authoring fields each exposes
component-models.jsonThe schemas (data models) referenced by definitions
component-filters.jsonWhich blocks are allowed inside which container (sections, columns, etc.)

UE reads these on load and uses them to render the authoring sidebar, validate input, and limit insertion choices.

component-definition.json

Lists every component (block) the editor should know about. Each entry has an id, a display title, and a plugins.xwalk.page.template block that says what kind of markup the block produces.

component-definition.json
{
"groups": [
{
"title": "Blocks",
"id": "blocks",
"components": [
{
"title": "Hero",
"id": "hero",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Hero",
"model": "hero",
"filter": "hero"
}
}
}
}
},
{
"title": "Cards",
"id": "cards",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Cards",
"model": "cards",
"filter": "cards"
}
}
}
}
}
]
}
]
}

component-models.json

Defines the authoring fields for each component referenced above. This is what authors actually fill in.

component-models.json
{
"models": [
{
"id": "hero",
"fields": [
{
"component": "reference",
"valueType": "string",
"name": "image",
"label": "Background image",
"multi": false
},
{ "component": "text", "name": "imageAlt", "label": "Alt text" },
{ "component": "text", "name": "title", "label": "Heading" },
{ "component": "richtext", "name": "body", "label": "Body" },
{ "component": "aem-content", "name": "cta", "label": "CTA link" }
]
},
{
"id": "cards",
"fields": [
{ "component": "container", "name": "cards", "label": "Cards" }
]
}
]
}

Common field components: text, multiline, richtext, select, boolean, reference (for assets), aem-content (for content fragments / pages), container (for nested children).

component-filters.json

Whitelists which components can be inserted inside which container.

component-filters.json
{
"filters": [
{
"id": "section",
"components": ["hero", "cards", "columns", "text", "image", "embed"]
},
{
"id": "cards",
"components": ["card-item"]
},
{
"id": "hero",
"components": []
}
]
}

The id matches the template.filter value in component-definition.json. An empty list means "leaf" -- the block accepts no nested children.

Instrumentation: data-aue-* attributes

When UE loads a page, it inspects the rendered HTML for data-aue-* attributes to know what's editable. Your block decoration code adds these attributes so the editor can hover-highlight, edit, and re-order elements in place.

AttributeWhat it marks
data-aue-resourceThe content resource (where the value persists)
data-aue-propWhich property of that resource is being shown
data-aue-typeField component (e.g. text, richtext, image, reference)
data-aue-labelLabel shown in the editor sidebar
data-aue-filterFor container elements -- which filter applies here
data-aue-behaviorcomponent for a whole block, container for a nested zone

For document-based authoring, EDS injects these attributes automatically based on the table position. For UE, the source already comes pre-instrumented from AEM and your block decoration must preserve the attributes when restructuring the DOM.

A pattern that survives both modes:

/blocks/hero/hero.js
export default function decorate(block) {
const image = block.querySelector('picture');
const heading = block.querySelector('h1, h2, h3');
const cta = block.querySelector('a');

// Preserve any UE instrumentation by moving nodes, not cloning
block.innerHTML = '';

const content = document.createElement('div');
content.classList.add('hero-content');
if (heading) content.append(heading);
if (cta) content.append(cta);

if (image) block.append(image);
block.append(content);
}

block.innerHTML = '' then block.append(...) is safe because the children retained their original data-aue-* attributes -- you didn't clone, you re-parented.

Connecting to a content source

The site's fstab.yaml points at the AEM author URL:

fstab.yaml
mountpoints:
/: https://author-p12345-e67890.adobeaemcloud.com/

Plus a helix-config.yaml content section:

helix-config.yaml
content:
source:
type: aem
url: https://author-p12345-e67890.adobeaemcloud.com/

UE itself opens at https://experience.adobe.com/aem/editor and points at the same AEM instance. The editor then fetches the EDS preview of the page being edited and displays it inside an iframe.

Tradeoffs vs document-based authoring

AspectUniversal EditorDocument-based
Author UXVisual, in-contextWord / Docs
Field validationStrong (per-model)Weak (free text in tables)
TranslationAEM MSM, GLaaSManual or external service
Reusable fragmentsYes (Content Fragments)Limited (/fragments/ pattern)
Setup costHigh (AEM + UE config)Low (SharePoint / Drive folder)
Best forStructured content, governanceMarketing speed, low-skill authors

Common gotchas

SymptomLikely causeFix
Block not visible in UEMissing entry in component-definition.jsonAdd it; restart UE
Block visible but not insertable into a sectionMissing in component-filters.jsonAdd to the relevant filter
Edits don't persistdata-aue-resource / data-aue-prop lost during decorationDon't clone nodes; re-parent them
Field shows wrong widgetcomponent mismatch in component-models.jsonCheck valid components: text, richtext, reference, etc.
Multilingual content not loadingWrong cf (content fragment) reference pathCheck aem-content field's reference resolution

See also