Adobe Experience Manager
Checked: 2026-08-16
Adobe’s enterprise CMS. Not a CMS in the WordPress sense — a content repository with a web framework on top, where the URL is a path in a content tree. Currently mid-way through the biggest architectural change in its history.
§0 The one idea
Everything is a node in a tree, and the URL is the path to it.
There is no route table, no controller, no posts table. A request for /content/site/en/products.html resolves to the repository node at /content/site/en/products, reads a property on it saying what kind of thing it is, and finds a script for that type.
AEM the product
─────────────────────────────────────
Sling REST framework
maps URL → JCR resource
─────────────────────────────────────
OSGi (Apache Felix)
bundles · services
─────────────────────────────────────
JCR (Jackrabbit Oak)
the repository
everything is a node
vs a normal CMS: WordPress has a database with a posts table and a theme that queries it. AEM has a hierarchical content repository where content, configuration, code and users are all nodes in the same tree, and rendering is a lookup on a property.
vs a modern framework: there’s no build-and-deploy of pages. Content and code live in the same repository, versioned separately, deployed separately, and the boundary between “content” and “configuration” is a matter of which subtree you’re in.
Is it still relevant? Yes, and actively invested in. Adobe is rebuilding the delivery layer (Edge Delivery Services), the install base on 6.5 is being carried forward on a long-term-support line rather than dropped, and it remains the default CMS in large-organisation stacks. Its appearance in CRO and growth specs is usually because it’s the content layer Target personalises, not because CRO work happens in it.
At a glance
| What it is | Enterprise CMS, DAM, forms |
| Built on | Java · JCR (Oak) · Sling · OSGi |
| Template language | HTL (was Sightly), replaced JSP |
| Topology | Author → Publish → Dispatcher → CDN |
| The cache layer | Dispatcher — an Apache module |
| Current flavour | AEM as a Cloud Service (AEMaaCS) |
| Legacy flavour | AEM 6.5 / 6.5 LTS |
| The big shift | Edge Delivery Services |
| Headless | Content Fragments + GraphQL |
| The CRO link | Experience Fragments → Target offers |
The topology
Three environments, and the separation is the thing to understand.
AUTHOR authors work here
│ in-context editing
│ versioning, workflow
│ replication
↓
PUBLISH renders pages for visitors
│ no authoring UI
↓
DISPATCHER cache + security filter
│ an Apache HTTP module
↓
CDN
↓
visitor
The Dispatcher is the performance layer and it is not optional. It caches rendered HTML on disk and filters which URLs are allowed to reach Publish at all. A page that misses the Dispatcher cache is rendering Java on a request — the difference between a cache hit and a miss is not marginal.
Two practical consequences worth carrying:
- Cache invalidation is a design problem. Publishing a page flushes it, but a shared component change can mean flushing broadly. Aggressive invalidation destroys the cache; conservative invalidation serves stale content. This is where a lot of AEM performance work actually lives — Caching Strategies, Core Web Vitals
- The Dispatcher filter is a security boundary. Misconfigured, it exposes repository paths and admin endpoints. It is a routine finding on AEM penetration tests
Content structure
Everything lives in one tree, which is unusual and worth seeing:
/content pages · assets · fragments
/apps your components and templates
/libs Adobe's — never modify
/conf configuration, templates
/etc legacy configuration
/var runtime, workflows
/home users and groups
/apps overlays /libs. You never edit /libs; you place a matching path in /apps and yours wins. That’s the extension model for the whole product, and it’s why upgrades survive customisation better than they might.
What a node actually looks like
A page is not a database row. It’s a node with child nodes, serialised to XML on disk:
<!-- /content/site/en/products/.content.xml -->
<jcr:root jcr:primaryType="cq:Page">
<jcr:content
jcr:primaryType="cq:PageContent"
jcr:title="Products"
sling:resourceType="mysite/components/page">
<root sling:resourceType="…/responsivegrid">
<hero
sling:resourceType="mysite/components/hero"
title="Autumn range"
subtitle="New in"/>
</root>
</jcr:content>
</jcr:root>Read that once and the model lands. The page is a node. The hero the author dragged onto it is a child node. What they typed is a plain property on that node. sling:resourceType is the pointer to the code that renders it.
How a request resolves
GET /content/site/en/products.html
↓
resolve to the JCR node
/content/site/en/products
↓
read its sling:resourceType property
mysite/components/page
↓
find a script for that type + .html
/apps/mysite/components/page/page.html
↓
render HTL
URL = a content path, not a route
Selectors and extensions extend this, and they’re the part that makes AEM URLs look the way they do:
/content/site/en/products.print.html
│ │
│ └ extension
└ selector
→ /apps/mysite/components/page/print.html
/content/site/en/products.model.json
→ the same node, serialised as JSON
(this is how headless mode works)
One node, many representations. Add a selector, get a different script, same content. It’s a genuinely elegant model once it clicks.
Components and templates
Components
The unit of authoring. A component is a folder in /apps containing an HTL script, a dialog defining its authoring fields, and usually a Sling Model.
/apps/mysite/components/hero/
.content.xml the component's own node
hero.html HTL — the markup
_cq_dialog/ the author's fields
_cq_editConfig drag-drop behaviour
/core/…/models/Hero.java
Sling Model — the logic
The four files, minimally
1. The dialog — what the author sees. Granite UI, defined in XML, and its verbosity is a fair sample of AEM generally:
<!-- _cq_dialog/.content.xml, trimmed -->
<items jcr:primaryType="nt:unstructured">
<title
sling:resourceType="granite/ui/components/
coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"/>
▲
writes to the JCR property "title"
on this component's node
</items>2. The Sling Model — reads the node, holds the logic:
@Model(adaptables = Resource.class,
defaultInjectionStrategy = OPTIONAL)
public class Hero {
@ValueMapValue
private String title; // ← "title"
// from the node
@ValueMapValue
private String subtitle;
@OSGiService
private ProductService products;
public String getTitle() {
return title;
}
}@ValueMapValue is the whole idea: the field name matches the JCR property name, and Sling injects it. No query, no mapping layer.
3. The HTL — the markup:
<div class="hero"
data-sly-use.hero="com.mysite.models.Hero">
<h1>${hero.title}</h1>
<p data-sly-test="${hero.subtitle}">
${hero.subtitle}
</p>
<sly data-sly-list.item="${hero.links}">
<a href="${item.path @ context='uri'}">
${item.label}
</a>
</sly>
</div>HTL in four attributes, which is most of the language:
data-sly-use bind a Sling Model
data-sly-test render only if truthy
data-sly-list loop
data-sly-resource include another component
<sly> element removed from output
${… @ context} escaping context — the
XSS defence, and the reason
HTL replaced JSP
Everything is auto-escaped by default. That’s the headline feature: HTL is deliberately restricted so you cannot write business logic or an injection vulnerability into a template.
The round trip
The thing worth memorising, because it’s the whole component model in eight lines:
DIALOG name="./title"
↓ author types "Autumn range"
JCR NODE title="Autumn range"
↓ @ValueMapValue
SLING MODEL String title
↓ ${hero.title}
HTL <h1>Autumn range</h1>
Core Components are Adobe’s maintained, accessible, tested implementations of all of the above. Use them. The standard failure of an AEM project is rebuilding components Adobe already maintains, then owning the accessibility and upgrade burden forever — Design Systems.
Editable templates
Templates define which components are allowed where, and editable templates let that be configured in the UI rather than in code. Authors get a policy-constrained canvas rather than a free-for-all.
This is genuinely the good part of AEM: governance at scale. A large organisation with hundreds of authors across markets gets consistent structure without a developer in the loop for every page — and Multi Site Manager (MSM) propagates a master site’s content to localised variants with controlled inheritance.
Fragments — the confusion worth clearing
Two things with similar names doing entirely different jobs. This is the single most common AEM misunderstanding.
CONTENT FRAGMENT structured content
headless-first NO layout
typed fields: delivered as JSON
title, body, via GraphQL
image reference → for apps
EXPERIENCE FRAGMENT assembled experience
components + HAS layout
their layout exported as HTML
reusable block → for pages, and
for other channels
Content Fragment = the data. Experience Fragment = the rendered thing.
A Content Fragment is a product description with fields. An Experience Fragment is a promotional banner, fully laid out, ready to drop onto a page or ship somewhere else entirely.
The Target integration — the CRO-relevant part
This is why AEM shows up in CRO specs at all.
AEM Experience Fragment
↓ export
Adobe Target offer (HTML or JSON)
↓
used inside a Target activity
↓
served to a matched audience
Content Fragments export too, as
"Content Fragment Offers" (JSON) —
for personalising headless apps
Verified: Experience Fragments export to Target as HTML or JSON offers. Content Fragments under a Target-enabled folder export as Content Fragment Offers — the fully hydrated JSON — for use in Target activities serving headless applications.
What this means practically. In an Adobe shop, the content in a personalisation test is authored and governed in AEM, and the targeting and delivery happen in Target. A marketer builds the variant in AEM, exports it, and runs the activity in Target without a developer.
What it doesn’t mean. The experiment design, the statistics and the reading of results are all Target’s problem, not AEM’s — Adobe Target, Personalisation Tests.
Headless
AEM’s headless story is Content Fragments delivered over a GraphQL API — a customised implementation based on standard GraphQL, with content models defining fragment structure.
Practical shape:
- Content models define types and fields; fragments are instances
- Persisted queries are the production pattern — the query is stored server-side and called by name, so it can be cached at the CDN rather than sent as an arbitrary POST
- The Assets API handles binary delivery
# stored as a persisted query, then
# called by name from the front end
query getProduct($path: String!) {
productByPath(_path: $path) {
item {
name
price
description { plaintext }
image { ... on ImageRef { _path } }
}
}
}Note _path and productByPath — the query is still addressing content by repository path. Even in headless mode, the tree is the model.
It works, and it is not why anyone buys AEM. If headless content delivery is the whole requirement, a purpose-built headless CMS is lighter and cheaper. AEM earns its cost on governance, workflow, DAM and scale — Headless Architecture.
Edge Delivery Services — the big shift
The largest architectural change AEM has made, and the thing most likely to be new to someone who last touched AEM a few years ago.
Edge Delivery Services (EDS) is a composable delivery layer that decouples authoring from rendering, with two very different authoring routes:
DOCUMENT-BASED AUTHORING
author edits a Word or Google Doc
↓
change published to the live site
↓
no AEM authoring UI involved at all
UNIVERSAL EDITOR
WYSIWYG, in-context, on a live preview
content from AEM — governance intact
Document-based authoring is the genuinely surprising one. Content is authored in Word or Google Docs by people who need no training in a CMS, and Edge Delivery turns a document change into updated content on the live site. For an enterprise CMS whose historical reputation is complexity, that’s a striking reversal.
What a “block” is in a document
The mechanism, and it’s the bit that makes EDS click. A block is a table in the document, with a merged first row naming it:
IN THE WORD / GOOGLE DOC
┌───────────────────────────────┐
│ Hero │ ← merged
├──────────────┬────────────────┤ = name
│ image.jpg │ Autumn range │
└──────────────┴────────────────┘
every row → a div
every column → a div
<!-- becomes, in the DOM -->
<div class="hero">
<div>
<div><img src="image.jpg"></div>
<div>Autumn range</div>
</div>
</div>YOUR PROJECT CODE
/blocks/hero/hero.js decorates the DOM
/blocks/hero/hero.css styles it
Verified: blocks are authored as tables whose merged first row identifies the block type; each subsequent row becomes a div and each column becomes a div; parentheses after the name pass options. Project CSS and JavaScript live per-block under /blocks/.
The value is the content structure, not the code — you’ll rewrite the JS and CSS for your own project, but the table-to-div contract is what lets a marketer in Word produce a structured component.
Adobe’s own guidance on choosing (from 2026 material): document-based authoring where authors already live in Word and Google Docs and speed matters, or for new and lean sites; the Universal Editor where you’re running existing sites and need governance at scale.
Why it matters beyond authoring: EDS is built for speed, and sites on it are typically fast in a way traditional AEM sites are not. If you meet AEM in a performance context, “are they on Edge Delivery” is the first question — Core Web Vitals, Rendering Strategies.
Deployment flavours, and the live dates
Which AEM someone means matters enormously, and there are currently four answers.
AEM as a Cloud Service current · evergreen
continuous updates, Adobe-run
AEM 6.5 LTS the carried-forward
long-term support line path for 6.5
AEM 6.5 on Adobe Managed Services
core support ends 31 AUGUST 2026
AEM 6.5 on-premises
core support ends FEBRUARY 2027
Verified: for Adobe Managed Services customers, AEM 6.5 core support ends by 31 August 2026; for on-premises customers it’s currently planned for February 2027, with support continuing to 28 February 2027. Both paths are covered by AEM 6.5 LTS.
The Managed Services date is two weeks away. Any organisation still on 6.5 under AMS is either on 6.5 LTS already, mid-migration to Cloud Service, or about to have a conversation. In any brief, “AEM 6.5” versus “AEM as a Cloud Service” tells you which of those a team is living in.
Cloud Manager is the CI/CD and deployment layer for AEMaaCS — pipelines, code quality gates, and environment management. On Cloud Service you don’t get server access; Cloud Manager is the interface to deployment.
Where it’s strong
- Governance at scale. Templates with policies, workflow, approvals, MSM for multi-market. Nothing in the mid-market touches this
- The DAM. AEM Assets is a genuine digital asset manager with metadata, renditions and Dynamic Media, not a media library
- Multi-site, multi-language. Inheritance and rollout across dozens of markets is the problem it was built for
- Stack integration. Assets flow to Target, audiences flow from Real-Time CDP, analytics flow to Adobe Analytics
- Edge Delivery is a real answer to the “AEM sites are slow” criticism
Where it hurts
- The learning curve is genuinely steep, and it’s Java. JCR, Sling, OSGi and HTL are four unfamiliar things at once, and none transfer from other CMS work
- Cost. Licensing, plus specialist developers who command a premium precisely because the curve is steep
- Implementation quality varies wildly. The gap between a good AEM build and a bad one is larger than in most platforms, and a bad one is very expensive to live in
- Performance is not free. A traditional AEM site with a poorly-tuned Dispatcher is slow, and that reputation is the reason Edge Delivery exists
- Overkill is the normal failure. Many AEM implementations are running a fraction of the platform and would have been better served by something smaller
What to actually know for a spec
If AEM appears in a CRO or growth role, the useful questions are:
- Which flavour? Cloud Service, 6.5, or 6.5 LTS — this tells you the team’s decade
- Edge Delivery, or traditional? Determines whether performance is a live problem
- Is the Target integration wired up? Experience Fragment export is the difference between marketers shipping personalisation themselves and every variant being a developer ticket
- Core Components, or custom everything? Predicts the accessibility and upgrade burden
You are unlikely to be authoring AEM components. You are quite likely to need to know why a content change takes a Dispatcher flush to appear, and why the variant you asked for is a two-week ticket.
Related
- Adobe Target — where the personalisation actually happens
- Adobe Experience Cloud — how the suite fits together
- Headless Architecture — the alternative shape, and AEM’s own headless mode
- Caching Strategies — the Dispatcher is a caching problem wearing a CMS
- Design Systems — Core Components as a maintained library