---
name: aem-universal-editor
description: >
  Builds and instruments applications for the Universal Editor — the in-context authoring surface for AEM Sites pages, Edge Delivery Services sites and headless front ends. Covers data-aue-* instrumentation, the AEM connection and CORS handshake, component and block model JSON, extensions, and the preview-to-publish flow. Use this whenever the user adds data-aue-resource, data-aue-prop, data-aue-type, data-aue-filter, data-aue-label or data-aue-model attributes, writes the urn:adobe:aue:system:aemconnection meta tag or loads universal-editor-service.adobe.io/cors.js, builds an Edge Delivery project from aem-boilerplate-xwalk with paths.json and per-block model, definition and filter JSON, asks why a component is not editable or not selectable in the editor, migrates off the deprecated SPA Editor, or makes a Next.js or React app editable against AEM. For the content API itself use `aem-headless`; for block behaviour use `aem-edge-delivery-services`.
metadata:
  technology: AEM
  type: development
---

# AEM Universal Editor
> **Targets:** AEM as a Cloud Service 2026.x · Universal Editor (all AEM-backed front ends) · **Verified:** 2026-09 against https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/getting-started

The Universal Editor edits *your* rendered application, not an AEM-specific rendering of it. You describe what each piece of markup maps to in the content repository, and the editor builds the authoring UI from that. So the work is almost entirely instrumentation and content modelling: get the resource URNs and property names right and everything else follows; get them wrong and the page looks fine to a visitor and is uneditable to an author.

SPA Editor is deprecated (AEMaaCS 2025.01, 6.5.23; P1/P2 and security fixes only). Universal Editor is the successor for every new project.

## 1. Decide first

| Question | Default | Change when |
|---|---|---|
| Which front end | Edge Delivery Services with the `adobe-rnd/aem-boilerplate-xwalk` template | AEM Sites HTL when you need the full component/policy model; a custom React/Next app when the front end is owned elsewhere |
| Instrumenting HTL components | Nothing to do — Core Components ≥ 2.24 already emit `data-aue-*` | Hand-write attributes only for custom components that do not extend a Core Component |
| Where the content lives | The AEM repository the `aemconnection` meta tag points at | Headless apps still edit Content Fragments — same attributes, different resource URNs |
| Model definition for EDS | Per-block `_<block>.json` (model, definition, filter) in the block folder | A single central model file only for a tiny site |
| Editable unit | `data-aue-type="component"` on the block, `prop` on each field | `container` when authors add and reorder children |
| Auth for the editor | The author's own IMS login through the editor | Service credentials never belong in the front end |
| Publishing | Author edits → preview → publish through the editor's own flow | A separate CI publish only for code, never for content |

## 2. Instrument the markup

Four attributes carry the whole contract:

```html
<div data-aue-resource="urn:aemconnection:/content/site/en/home/jcr:content/hero"
     data-aue-type="component"
     data-aue-label="Hero">
  <h1 data-aue-prop="title" data-aue-type="text">Summer collection</h1>
  <div data-aue-prop="body" data-aue-type="richtext">…</div>
  <img data-aue-prop="image" data-aue-type="media" src="/content/dam/site/hero.jpg" alt="">
</div>
```

- `data-aue-resource` — the resource the node maps to, as a URN. Everything below it is addressed relative to it, so one wrong path silently disables a whole subtree.
- `data-aue-prop` — the property name on that resource. It must match the stored property exactly, including case.
- `data-aue-type` — how to edit it: `text`, `richtext`, `media`, `container`, `component`, `reference`.
- `data-aue-label`, `data-aue-filter`, `data-aue-model` — the name authors see, which components may be added inside a container, and which model backs the item.

Full attribute and type semantics, plus the nesting rules for containers: `references/instrumentation.md`.

## 3. Connect the app to AEM

Two tags in the document head, and nothing else:

```html
<meta name="urn:adobe:aue:system:aemconnection" content="aem:https://author-p1234-e5678.adobeaemcloud.com">
<script src="https://universal-editor-service.adobe.io/cors.js" async></script>
```

- The connection meta tag names the **author** environment — the editor writes there. Point it at publish and nothing saves.
- `cors.js` performs the cross-origin handshake between your app and the editor service. Without it the page renders normally and the editor sees no editable content at all — the single most common "why is nothing editable" cause.
- The AEM author environment must allow your app's origin. A CORS failure here looks identical to missing instrumentation, so check the browser console before re-reading your attributes.
- Ship instrumentation only where it is wanted: `data-aue-*` in a public production build exposes repository paths.

## 4. Content models

**AEM Sites (HTL).** Core Components ≥ 2.24 emit the attributes themselves; a custom component needs them added to its HTL, and its dialog still defines the fields.

**Edge Delivery Services (xwalk).** Start from `adobe-rnd/aem-boilerplate-xwalk`. Each block carries its own JSON beside its code:

- **model** — the fields an author edits, and their types.
- **definition** — how the block appears in the editor's component list.
- **filter** — which components may be placed inside it.

`fstab.yaml` points at the AEM author environment rather than a document source, and `paths.json` maps repository paths to site routes. Get `paths.json` wrong and pages render but resource URNs do not resolve.

Worked block JSON for all three files: `references/xwalk-block-models.md`.

## 5. Extensions

The editor's UI is extensible through App Builder extensions — custom rail panels, toolbar actions and field types. Reach for one only when authors need something the standard editor cannot express; an extension is an application to own and upgrade, not configuration.

## Deliverable format

```
## Target — which front end, which AEM environment, which content model
## Instrumentation — the markup with data-aue-* attributes, per component or block
## Connection — meta tag, cors.js, CORS policy on author, and where each is set
## Models — block/component model, definition and filter JSON (or the dialog for HTL)
## Paths — paths.json mapping (EDS) or the resource URN scheme (Sites/headless)
## Verification — what to click in the editor to prove each field is editable
## Open questions
```

## Checklist

- [ ] Every editable region has a `data-aue-resource` that resolves in the target environment.
- [ ] Every `data-aue-prop` matches the stored property name exactly, including case.
- [ ] Every prop has a `data-aue-type`, and containers declare a `data-aue-filter`.
- [ ] The `aemconnection` meta tag points at **author**, not publish.
- [ ] `cors.js` is loaded, and author allows the app's origin.
- [ ] EDS: each block has model, definition and filter JSON, and `paths.json` maps its routes.
- [ ] Authors can add, reorder and delete children wherever the design allows it.
- [ ] Instrumentation is absent, or harmless, in the public production build.
- [ ] The change was verified in the editor, not only in the rendered page.

## Anti-patterns

| Anti-pattern | Why it hurts | Fix |
|---|---|---|
| Pointing `aemconnection` at the publish tier | Edits appear to work and never persist | Point at the author environment |
| Omitting `cors.js` | Nothing is editable, with no error on the page | Load it in the head, `async` |
| Hand-instrumenting Core Components | Duplicate and conflicting attributes | Upgrade to Core Components ≥ 2.24 and delete the hand-written ones |
| `data-aue-prop` guessed from the label | Silently unsaveable field | Read the property name from the model or the node |
| A container with no `data-aue-filter` | Authors can insert anything, including components that break the layout | Declare the allowed components |
| Starting a new project on SPA Editor | Deprecated since AEMaaCS 2025.01; fixes only | Use the Universal Editor from the start |
| Building an extension for a small gap | An app to maintain forever | Model it in the block JSON first |

## Go deeper

- `references/instrumentation.md` — every attribute and type, nesting and container rules, and the failure each mistake produces.
- `references/xwalk-block-models.md` — model, definition and filter JSON for an EDS block, with `paths.json` and `fstab.yaml`.
- Sibling skills: `aem-edge-delivery-services` for block code and performance; `aem-headless` for the content API; `aem-component-development` for HTL components and dialogs.
