Tags: web-dev platform

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 isEnterprise CMS, DAM, forms
Built onJava · JCR (Oak) · Sling · OSGi
Template languageHTL (was Sightly), replaced JSP
TopologyAuthor → Publish → Dispatcher → CDN
The cache layerDispatcher — an Apache module
Current flavourAEM as a Cloud Service (AEMaaCS)
Legacy flavourAEM 6.5 / 6.5 LTS
The big shiftEdge Delivery Services
HeadlessContent Fragments + GraphQL
The CRO linkExperience 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.