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.
When agents use itCovers 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`.
Install
Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.
npx skills add AGCO-Global/org-skills --skill aem-universal-editor -a github-copilot
# or with the org installer (adds .github/skills/aem-universal-editor):
npx -y github:AGCO-Global/org-skills add skill aem-universal-editor
Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.
Installs the aem-development-skills plugin, which bundles all AEM / Development skills and keeps them updated.
/plugin marketplace add AGCO-Global/org-skills
/plugin install aem-development-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill aem-universal-editor -a claude-code
Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.
Installs into .agents/skills/, which Codex reads.
npx skills add AGCO-Global/org-skills --skill aem-universal-editor -a codex
Installs into Cursor's skills folder.
npx skills add AGCO-Global/org-skills --skill aem-universal-editor -a cursor
Installs into Gemini CLI's skills folder.
npx skills add AGCO-Global/org-skills --skill aem-universal-editor -a gemini-cli
Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.
Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.
Try it — example prompts
Prompts this skill is tested against, and what a good answer includes.
We author in AEM with Universal Editor; how do I define the block model?
adobe-rnd/aem-boilerplate-xwalk templateper-block JSON with definitions, models and filterspaths.json maps repository paths to site routesfstab.yaml points at the AEM author environmentfield name becomes the property the block code reads
I added data-aue-resource and data-aue-prop to my React app but nothing is editable in the Universal Editor.
checks the universal-editor-service.adobe.io/cors.js script is loadedchecks the urn:adobe:aue:system:aemconnection meta tagconnection must point at author, not publishauthor environment must allow the app's origin (CORS)verifies the resource URN resolves in that environment
Make this custom HTL teaser component editable in the Universal Editor.
data-aue-resource on the component element and data-aue-prop per fielddata-aue-type of text, richtext or media per fielddata-aue-label for the name authors seeCore Components 2.24+ already emit the attributes, so only custom components need them
We're on the SPA Editor with React. What should we do for a new site section?
SPA Editor is deprecated (AEMaaCS 2025.01, 6.5.23), P1/P2 and security fixes onlyUniversal Editor is the named successor for new workinstrument the existing React app with data-aue-* rather than extending SPA Editor
Authors need to add and reorder cards inside our carousel block.
data-aue-type="container" on the element holding the childrendata-aue-filter naming the allowed child componentsa filter entry in the block JSON matching the attributeeach card is a component with its own resource
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
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.
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.
---
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.