AEM

Start here if AEM is new to you: how Author, Publish, and Dispatcher fit together, plus ready-to-copy starters — then the JCR repository, Sling's resource-driven request model, templates and components, authoring, workflows, and replication in depth.

Every AEM term at a glance

Every entry, one line each. Highlighted ones have a detailed example below — click to jump there.

All 73 entries
Node
The basic unit of storage in the repository — everything in AEM is a node
Property
A named, typed value held on a node
Node type
Declares what a node is and which properties/child nodes it may hold
Mixin type
An additional type mixed onto a node for a cross-cutting capability, e.g. mix:versionable
jcr:primaryType
The property recording a node's own node type
jcr:content
The child node holding a page or asset's actual content and properties
Workspace
A named, isolated tree within the repository
Session
An authenticated connection used to read and write repository content
Versioning
Snapshotting a node's history so past states can be restored or compared
CRXDE Lite
The browser-based tool for browsing and editing the raw node tree
Node path
A node's address in the repository, e.g. /content/myco/us/en
JCR-SQL2
A SQL-like query language for finding nodes by property or path
Apache Oak
The modern storage engine implementing AEM's JCR repository
HTL (Sightly)
AEM's template language for pulling data into component markup
Sling Model
A plain Java object adapted from a resource, holding a component's logic
Client library (clientlib)
A packaged, categorized bundle of a component's CSS and JavaScript
cq:dialog
A component's authoring edit dialog, defined as a node under the component
Design dialog
A dialog editing template-level defaults for a component, not page content
Edit config (cq:editConfig)
Configures a component's in-page toolbar and listeners
Component group
The category a component appears under in the authoring component browser
sling:resourceType
Points a resource at the component or script that renders it
sling:resourceSuperType
Points a component at a base component it inherits scripts/dialogs from
Core Components
Adobe's standard, extensible open-source component library
Proxy component
A thin project component that extends a Core Component for policy mapping
Use-API
The object model (often a Sling Model) an HTL template binds via data-sly-use
Resource
Sling's uniform abstraction over a node — anything addressable by a path
Resource resolver
Maps a request URL to a resource and back, following the resource path
Resource type
The sling:resourceType value that determines what renders a resource
Selector
Extra dot-separated URL segments used to choose an alternate rendering script
Extension (URL)
The final URL segment (e.g. .html, .json) selecting the response format
Suffix
Extra path appended after the resource path and extension, for parameterized requests
Sling servlet
A servlet registered against a resource type instead of a URL pattern
Adapter factory
Registers how one type (e.g. Resource) can be adapted into another
Script resolution
Sling's search of /apps then /libs for the script matching a request
Overlay
Copying a /libs script or dialog to /apps so the copy wins resolution
Sling Scheduler
Runs jobs on a cron-like schedule inside the Sling/OSGi runtime
OSGi bundle
A deployable, versioned module of Java code running inside the Sling container
Editable template
A template whose structure and policies authors can configure without code
Template structure
The fixed, locked layout and components every page from a template shares
Template policy
A named configuration, like allowed components, applied to a component on a template
Content policy mapping
Links a specific component instance on a page to the policy governing it
Page properties
A page's own metadata — title, description, tags — held on its jcr:content node
Responsive grid
The default layout container new templates place components into
Content Fragment
Structured, channel-agnostic content authored independently of any page layout
Experience Fragment
A reusable, pre-laid-out piece of experience shared across pages or channels
Live Copy
A page tree kept in an ongoing, syncable relationship with a source tree
Rollout
Pushing changes from a Live Copy source down to its dependent copies
Template editor
The console where architects define an editable template's structure and policies
Component group
Groups related components together for the component browser and picker
Workflow model
A graph of steps automating or routing review and processing tasks
Workflow step
One node in a workflow model, either participant-driven or automated
Participant step
Creates an inbox item and pauses the workflow until a person acts
Process step
Runs a Java workflow process class automatically, with no person involved
Workflow launcher
A rule starting a workflow automatically on an event, e.g. page creation
Inbox
An author's list of pending workflow participant tasks assigned to them
Payload
The content path a running workflow instance is acting on
Launch
A private, versioned copy of content staged for review before going live
Promote (launch)
Merges a launch's staged edits back onto the original live pages
Page version
A saved snapshot of a page that can be compared to or restored later
Workflow console
The admin UI for monitoring and managing running workflow instances
Escalation
Automatically reassigning or flagging a workflow step that misses its due date
Replication agent
Author-side configuration defining where and how content is pushed to a target
Activate
The replication action that pushes a piece of content live
Deactivate
The replication action that pulls a previously published piece of content back
Replication queue
A per-agent queue of pending replication jobs, retried until acknowledged
Dispatcher
AEM's reverse-proxy cache and load balancer sitting in front of publish
Dispatcher cache
The on-disk store of previously rendered .html responses the dispatcher serves from
Flush rule
Configuration deciding which cached paths a replication event invalidates
Invalidate
Marking a cached file as stale so the next request re-renders it from publish
Auto-invalidate
Also invalidating a changed path's ancestor folders, not just the exact file
Filter rules (dispatcher.any)
Rules deciding which request paths are even allowed to reach publish
Sticky connection
Pinning a client to the same publish/dispatcher instance across requests
Static vs dynamic request
Whether the dispatcher can cache a response or must always ask publish

Worked examples with live previews, for the parts worth seeing in action.

Starter Templates

How AEM Fits Together

If you’re coming from a typical web stack, the closest analogy is: Author is your CMS admin and staging environment in one; Publish is your production web server; Dispatcher is the CDN/reverse-proxy cache sitting in front of it. A content change only reaches real visitors once it’s copied (“replicated”) from Author to Publish, and even then, Dispatcher’s cache has to be told to drop its old copy before that change is actually visible — which is why “I published it but the site still shows the old version” is almost always a Dispatcher problem, not a content problem.

Underneath both Author and Publish sits JCR, the repository — every page, component, and asset is a node in one big tree, addressed by a path much like a file path. Sling is what turns an incoming request URL into one of those nodes and picks a script to render it. That script is almost always a component: an HTL template (the view) paired with a cq:dialog (the form an author fills in) and, for anything non-trivial, a Sling Model (a small Java class that hands the template already-computed data).

That’s the whole loop: an author edits component instances on a page in Author, using dialogs backed by JCR properties; Sling resolves each request to those nodes and their templates; and once published, Dispatcher decides whether Publish even needs to do that work again for the next visitor. Every category on this page is one piece of that loop, in roughly the order you’d meet them building a first component: the repository these pieces live in, then components and templates themselves, then Sling’s resolution and authoring layers, then how it all reaches a real visitor.

The big picture before the details: what Author, Publish, and Dispatcher each do, and where JCR, Sling, and components sit inside that.

Author
The instance where content is created and edited — never seen directly by real visitors
Publish
The instance that serves the live site — only shows content explicitly sent to it from Author
Dispatcher
A cache sitting in front of Publish — like a CDN — so most visitor requests never reach Publish at all
JCR
The repository both Author and Publish store content in — one big tree of nodes, similar to a filesystem
Sling
The framework that turns an incoming URL into a JCR node, then picks a script to render it
  • overview
  • author
  • publish
  • dispatcher
  • beginner

Project Setup Commands

# Scaffold a new project from Adobe's Maven archetype (prompts for
# groupId, artifactId, package, and app title)
mvn -B archetype:generate \
  -D archetypeGroupId=com.adobe.granite.archetypes \
  -D archetypeArtifactId=aem-project-archetype \
  -D archetypeVersion=LATEST

cd my-app

# Build every module and install it onto a local AEM instance
# running on localhost:4502
mvn clean install -PautoInstallSinglePackage

The generated project has separate modules for Java code (core), the /apps content your components/templates live under (ui.apps), and sample /content (ui.content) — everything from here on assumes that structure. Re-run the install command after any change to see it on your local AEM author instance.

The commands for scaffolding a new AEM project from the standard Maven archetype and getting it installed on a local instance.

AEM archetype
Adobe's official Maven archetype — generates the standard multi-module project layout (core, ui.apps, ui.content, ...)
ui.apps
The module that deploys to /apps — your actual component and template code
autoInstallSinglePackage
A Maven profile that builds and installs straight onto a running local AEM instance in one command
  • setup
  • maven
  • archetype
  • cli

New Component Starter

/apps/myco/components/content/hello/
  .content.xml   <- the component definition itself
  _cq_dialog/
    .content.xml <- the authoring dialog (see the Dialog Starter)
  hello.html     <- the HTL script that renders it
<!-- hello/.content.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
    xmlns:cq="http://www.day.com/jcr/cq/1.0"
    jcr:primaryType="cq:Component"
    jcr:title="Hello"
    componentGroup="MyCo Content"/>
<!-- hello/hello.html -->
<div data-sly-use.model="com.myco.core.models.HelloModel" class="hello">
  <h2>${model.title}</h2>
</div>

Copy this folder as the starting point for any new component: rename hello, replace the HTL body with your own markup, and add the _cq_dialog from the Dialog Starter once you know which fields it needs to expose to authors.

The three files every new component needs at minimum — its definition, its dialog, and its HTL script — in one place.

.content.xml
The node itself — declares the component's title, group, and resource supertype (if any)
jcr:title
What shows up as the component name in the authoring component browser
componentGroup
Which tab of the component browser this component appears under
  • component
  • htl
  • cq:dialog
  • boilerplate

Dialog (cq:dialog) Starter

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Properties"
    sling:resourceType="cq/gui/components/authoring/dialog">
  <content jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/container">
    <items jcr:primaryType="nt:unstructured">
      <title jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
          fieldLabel="Title" name="./title"/>
      <image jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
          fieldLabel="Image" rootPath="/content/dam" name="./imagePath"/>
      <featured jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
          text="Featured" name="./featured"/>
    </items>
  </content>
</jcr:root>

Save this as _cq_dialog/.content.xml inside a component folder (see the New Component Starter). Every field’s name attribute is the property it writes on the component’s content node — a Sling Model reads ./title as just title, ./imagePath as imagePath, and so on.

The three field types nearly every first component dialog needs: a text field, an image/path picker, and a checkbox.

name="./title"
The leading ./ writes the value onto the component's own content node, not somewhere else
pathfield
Gives authors a repository browser instead of a free-text path — used for images, page links, tags
checkbox
Stores 'true'/'false' as a String property by default — read it accordingly in a Sling Model
  • cq:dialog
  • granite ui
  • authoring
  • boilerplate

Sling Model Starter

package com.myco.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = Resource.class)
public class HelloModel {

    @ValueMapValue
    private String title;

    public String getTitle() {
        return title;
    }
}

This is the floor every Sling Model starts from: one field, one injection annotation, one getter — used in HTL as ${model.title}. Once this works, add more @ValueMapValue fields for each new dialog property, and reach for the fuller Sling Models entry in Sling Framework once you need a computed value, a default, or an optional property that shouldn’t break the page when it’s missing.

The simplest version of a Sling Model — inject properties, expose getters, nothing computed yet.

@Model
Registers the class and says what it adapts from — Resource is the usual choice for a component
@ValueMapValue
Reads a JCR property straight into the matching field, by field name unless otherwise told
One getter per field
HTL can only call methods, not read fields directly, so every field needs its own getter
  • sling models
  • java
  • boilerplate

HTL Loop & Conditional Starter

<ul data-sly-test="${model.links}" data-sly-list.link="${model.links}" class="link-list">
  <li>
    <a href="${link.url}">${link.label}</a>
  </li>
</ul>

The outer data-sly-test="${model.links}" drops the whole <ul> from the output when the list is empty or null, so you never ship an empty wrapper. data-sly-list.link="${model.links}" then repeats the <li> once per item, binding each one to link — every item also gets linkList.index, linkList.count, linkList.first, and linkList.last for free if you need them.

This is usually enough for a list of child resources or a Sling Model’s List<SomeType> getter. For the deeper explanation of auto-escaping and how the Use-API object behind model gets built, see HTL (Sightly) Basics in Templates & Components.

The two block statements every first component needs next: data-sly-list to repeat markup, data-sly-test to hide it.

data-sly-list
Repeats its element once per item, exposing itemList (or your chosen name) plus index/count/first/last helpers
data-sly-test
Removes its element entirely from the output when the expression is falsy — empty lists count as falsy
Two attributes, one element
Block statements are just HTML attributes, so a test and a list can sit on the same or nested elements
  • htl
  • sightly
  • data-sly-list
  • data-sly-test
  • boilerplate

Client Library (clientlib) Starter

/apps/myco/clientlibs/clientlib-base/
  .content.xml   <- declares the categories property
  css.txt        <- list of CSS files to include, in order
  js.txt         <- list of JS files to include, in order
  css/
    base.css
  js/
    base.js
<!-- clientlib-base/.content.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[myco.base]"
    embed="[myco.dependency]"/>
<!-- component HTL: pull the CSS in the <head>, JS at the end of <body> -->
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html">
  <sly data-sly-call="${clientlib.css @ categories='myco.base'}"/>
</sly>

categories is the name everything else refers to — another clientlib’s embed property, or HTL’s data-sly-call, both target it by string, not by path. css.txt/js.txt are plain file lists, so a new stylesheet under css/ does nothing until you add its line there. Call the same clientlib.html template with clientlib.js instead of clientlib.css to pull in the JS bundle wherever it belongs on the page.

The minimal clientlib folder structure and the HTL pattern that loads its CSS/JS into a component.

categories
The clientlib's public name — what other clientlibs embed and what HTL asks for by data-sly-call
css.txt / js.txt
One file per line, in load order — not a folder listing, so a new file must be added here to be picked up
clientlib.html template
Adobe's standard include — resolves to a <link>/<script> tag for the requested category
  • clientlib
  • css
  • javascript
  • htl
  • boilerplate

Sling Servlet Starter

package com.myco.core.servlets;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;

import java.io.IOException;

@Component(service = javax.servlet.Servlet.class)
@SlingServletResourceTypes(
    resourceTypes = "myco/components/content/hero",
    methods = "GET",
    extensions = "json")
public class HeroDataServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request,
                          SlingHttpServletResponse response) throws IOException {
        response.setContentType("application/json");
        response.getWriter().write("{\"status\":\"ok\"}");
    }
}

Requesting <hero-component-path>.json now hits doGet instead of the component’s usual HTL rendering. Reach for this instead of a Sling Model when the data isn’t sitting on the resource itself — an internal API call, a computed response an author’s browser fetches after the page loads, or any other endpoint a component needs on the side.

The smallest OSGi servlet that answers a GET request with JSON — for data a Sling Model alone can't reach.

@SlingServletResourceTypes
Binds the servlet to a resourceType (and optionally method/extension) instead of a fixed URL path
resourceType
Must match a real node's sling:resourceType — this is what makes Sling route requests to the servlet
doGet
Standard servlet method override — write straight to the response, same as any other Sling servlet
  • sling
  • servlet
  • osgi
  • java
  • boilerplate

Content Repository (JCR)

The JCR Node Tree

/content/myco/us/en/products                    <- cq:Page
  /jcr:content                                   <- cq:PageContent
    jcr:title = "Products"
    sling:resourceType = "myco/components/page/products"
    /hero                                        <- component node (nt:unstructured)
      sling:resourceType = "myco/components/content/hero"
      title = "Our Products"
    /content_par                                 <- container node
      /text                                      <- component node
        sling:resourceType = "myco/components/content/text"

Everything renderable in AEM, whether it’s a page, a DAM asset, or a single component instance, is just a node somewhere in this tree. The split between the page node and its jcr:content child exists so the page can be addressed and moved as a unit while its properties and component instances stay nested underneath, out of the way of the page’s own identity.

Because it’s one tree, a path fully describes where content lives and how it nests — a component under content_par is a child of that container, which is a child of jcr:content, which is a child of the page. Reordering or copying content is, structurally, just moving nodes around.

AEM content is one hierarchical tree of nodes, and every resource path an author or developer sees is really just a node path underneath it.

jcr:content
The child node holding a page's actual properties and components — the page node itself only marks the address
Node path
A node's address in the repository, e.g. /content/myco/us/en/products/jcr:content/hero
CRXDE Lite
The browser-based tree explorer for inspecting and editing nodes and properties directly
  • jcr
  • node
  • repository
  • content tree

Nodes, Properties & Node Types

A node’s primary type is fixed at creation and answers “what kind of thing is this” — a page, an asset, a folder, or the generic nt:unstructured that most component instances use because it doesn’t restrict which properties can be set on it. Node types are declared once, in a CND (Compact Node Type Definition), and every node in the repository must satisfy the one it claims.

Mixin types layer optional behavior on top without changing what a node fundamentally is. Adding mix:versionable to a node is what makes it eligible for version history at all; adding mix:lockable is what makes it lockable during editing. A node can carry several mixins at once, which is why two nodes with the same primary type can still support different capabilities.

Every piece of content in AEM is a node carrying typed properties, and its node type decides which properties are legal to hold.

jcr:primaryType
Declares a node's node type, e.g. nt:unstructured or cq:Page — every node has exactly one
Mixin type
An additional type mixed into a node for a cross-cutting capability, e.g. mix:versionable or mix:lockable
nt:unstructured
The permissive catch-all node type most AEM content and component nodes use, allowing arbitrary properties
Property type
JCR property types like String, Boolean, Date, and Reference, which constrain what a value can hold
  • jcr
  • node type
  • mixin
  • property

Templates & Components

HTL (Sightly) Basics

<div data-sly-use.model="com.myco.core.models.HeroModel"
     class="hero" data-sly-test="${model.title}">
  <h1>${model.title}</h1>
  <p data-sly-test="${model.description}">${model.description}</p>
  <a data-sly-test="${model.ctaLink}" href="${model.ctaLink}">
    ${model.ctaText}
  </a>
</div>

HTL deliberately has no way to run arbitrary Java or write to the response directly — every block statement is just an HTML attribute, and every expression is auto-escaped for its context (HTML, attribute, URI, or JavaScript), which is what closes off most of the templating-driven XSS holes older AEM sites picked up from JSP scriptlets.

Logic itself lives in the Use-API object bound by data-sly-use, almost always a Sling Model. The template stays a thin, declarative view over data the model already computed, which keeps a component’s markup readable even once its behavior gets complicated.

New to writing a component’s HTL from scratch? See the New Component Starter in Starter Templates for the minimal script, dialog, and definition together. For a minimal example of data-sly-list and data-sly-test working together, see the HTL Loop & Conditional Starter in Starter Templates.

HTL is the templating language AEM components use to pull data into markup, without embedding scriptlets in the page.

data-sly-use
Binds a Use-API object, often a Sling Model, to a named variable available for the rest of the block
Expression Language
${ } syntax reads properties into the markup, HTML-escaping the output by default
data-sly-test / data-sly-list
Block statements for conditional rendering and iteration, applied as plain HTML attributes
  • htl
  • sightly
  • templating
  • component

Component Structure & Dialogs

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/container">
  <items jcr:primaryType="nt:unstructured">
    <title
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
        fieldLabel="Title"
        name="./title"/>
  </items>
</jcr:root>

A component’s folder under /apps is really just a small collection of nodes: the .html HTL script that renders it, a cq:dialog describing its edit form, and a .content.xml declaring things like its title and group in the component browser. The name="./title" attribute on a field is what ties a dialog input straight to a property on the component’s own content node.

sling:resourceSuperType lets one component overlay another’s script or dialog without copying it — a common pattern is a project component that extends a Core Component and only overrides the one file it needs to change.

For a complete, ready-to-copy folder and a fuller dialog with common field types, see the New Component Starter and Dialog Starter in Starter Templates.

A component pairs an HTL template with a cq:dialog that gives authors the fields needed to edit the properties that template reads.

cq:dialog
A component's edit dialog, defined as a node literally named cq:dialog directly under the component
Granite UI
The form-field component framework (granite/ui/components/...) authoring dialogs are assembled from
sling:resourceSuperType
Points to a base component this one inherits from — AEM's equivalent of component-level subclassing
  • component
  • cq:dialog
  • granite ui
  • authoring

Sling Framework

Sling Resource Resolution

GET /content/myco/us/en/products.hero.html

1. Map the URL to a repository path via the resource resolver
     -> /content/myco/us/en/products
2. Read sling:resourceType from that resource's jcr:content
     -> myco/components/page/products
3. Selector "hero" + extension "html" narrow the script search:
     /apps/myco/components/page/products/hero.html    <- match found
     /apps/myco/components/page/products/html.html
     /apps/myco/components/page/products/products.html
4. Search order for the resource type itself: /apps first, then /libs

This lookup runs on every request, and it’s why the “wrong” template rendering almost always traces back to one of three things: the resource’s sling:resourceType points somewhere unexpected, a selector wasn’t matched by any script so a fallback rendered instead, or a project override under /apps isn’t actually shadowing the /libs version it was meant to replace.

The /apps over /libs search order is also what makes overlays possible — copying a script from /libs to the equivalent /apps path lets it win the resolution without touching the original product code at all.

How Sling turns an incoming request URL into a repository resource, then picks exactly which script renders it.

Resource type
A resource's sling:resourceType property — points to the component or servlet that knows how to render it
Selector
Extra dot-separated URL segments between the path and extension, used to pick an alternate rendering script
Script resolution
Sling matches resource type, selectors, and extension against scripts under /apps first, then /libs
  • sling
  • resource resolution
  • selector
  • servlet

Sling Models

@Model(adaptables = Resource.class, defaultInjectionStrategy = OPTIONAL)
public class HeroModel {

    @ValueMapValue
    private String title;

    @ValueMapValue
    private String ctaLink;

    private String ctaText;

    @PostConstruct
    protected void init() {
        ctaText = StringUtils.defaultIfBlank(ctaLink, "Learn more");
    }

    public String getTitle() { return title; }
    public String getCtaLink() { return ctaLink; }
    public String getCtaText() { return ctaText; }
}

Sling Models exist to keep component logic out of the HTL script entirely. data-sly-use.model in the template adapts the current resource into a model instance, and by the time the template sees it every field is already populated and any computed values, like ctaText here, are ready to read.

defaultInjectionStrategy = OPTIONAL matters in practice — without it, a missing property that a field expects fails the whole adaptation instead of just leaving that field null, which is rarely what a component author wants for content that’s allowed to be incomplete.

Writing your first model? Start with the plain Sling Model Starter in Starter Templates — one field, one getter — and add the injection strategy and computed values shown here once you actually need them.

A Sling Model adapts a resource or request into a plain Java object that a component template can call directly.

@Model
Registers the class and declares what it can adapt from, typically Resource or SlingHttpServletRequest
@ValueMapValue
Injects a single jcr property from the adaptable's value map straight into a field
@PostConstruct
Runs once after injection completes, the usual place to compute any derived values
  • sling models
  • java
  • adaptable
  • injection

Authoring & Editable Templates

Editable Templates & Policies

Before editable templates, page structure came from a JSP or HTL template file only a developer could touch. An editable template splits that same authority into a structure an architect locks down in the Template Editor, and initial content an author can freely rearrange afterward — so a marketing team can build new page layouts within the guardrails without filing a ticket for every new page type.

Policies are what make the guardrails granular rather than all-or-nothing. A responsive grid component might allow only three specific child components on one template and a completely different set on another, with both templates sharing the same underlying component code. The policy, not the component itself, is what changes between the two.

Editable templates let authors control page structure directly, with policies locking down what each component on that structure is allowed to do.

Template structure
The fixed layout and locked components an author cannot remove or rearrange on any page using the template
Template initial content
Editable starting content a new page receives, which authors are then free to change
Policy
A named configuration, such as allowed components or dialog options, applied to a component on a given template
Content policy mapping
The link between a specific component instance on a page and the policy that governs it
  • editable template
  • template policy
  • authoring
  • structure

Page Properties & Components on a Page

/content/myco/us/en/products/jcr:content
  jcr:title = "Products"
  jcr:description = "Our product catalog"
  cq:template = "/conf/myco/settings/wcm/templates/product-page"
  sling:resourceType = "myco/components/page/product"
  /root                                   <- responsive grid container
    sling:resourceType = ".../responsivegrid"
    /hero                                 <- dropped component instance
      sling:resourceType = "myco/components/content/hero"
      title = "Summer Collection"

The page properties dialog and the components an author drags onto the page edit two different sets of nodes that just happen to share a parent. Page properties write directly onto jcr:content itself — title, description, tags, the template reference — while every component instance is a sibling or descendant node placed inside a layout container like root.

That separation is why deleting every component off a page never touches its title or template reference, and why bulk-editing page metadata through a script can safely ignore the component subtree entirely.

A page's own metadata and the components authors drop onto it both live under the same jcr:content node.

cq:template
Recorded on jcr:content — tracks which template a page was created from
Responsive grid
The default layout container (.../responsivegrid) that most templates place components into
Page properties dialog
Edits jcr:content properties like jcr:title and jcr:description, never the components dropped on the page
  • page properties
  • jcr:content
  • component
  • responsive grid

Workflows & Launches

Workflow Models & Steps

Model: "Request for Activation"

[Start] -> [Participant: Review Content]   <- assigned to "reviewers" group
              |
       approved       rejected
              |             |
              v             v
   [Process: Activate Page]  [Participant: Notify Author]
              |                          |
              v                          v
            [End]                      [End]

Every step in a model operates on the same payload, so a participant step’s reviewer and a process step’s Java code are both looking at the same page path, just at different points in the sequence. Branching after a participant step is how approval and rejection paths diverge without needing two separate workflow models.

Workflow launchers are what turn this from something an author starts by hand into something that just happens — a launcher tied to page creation under a given path can kick off a review workflow the moment a new page is saved, before anyone remembers to trigger it manually.

A workflow model is a graph of steps that automate or route review, approval, and processing tasks against a piece of content.

Participant step
Creates an inbox item and pauses the workflow until a person acts on it
Process step
Runs a workflow process class automatically, with no person involved
Payload
The content path, usually a page, that a running workflow instance is acting on
Workflow launcher
A rule that starts a workflow automatically when an event occurs, such as a page being created
  • workflow
  • workflow model
  • participant step
  • process step

Launches: Staging Future Changes

A launch answers a specific problem: a set of pages needs to change, but not yet — a redesign timed to a product announcement, or seasonal content that should go live on a fixed date. Rather than editing the live pages early and hoping nobody notices, authors edit a full copy of the tree that lives entirely separately, under /content/launches, and only affects what visitors see once it’s promoted.

Because a launch is a real copy rather than a diff, it can be previewed and reviewed exactly as if it were the live site, catching layout or content issues before they ever reach production. Promoting the launch is what finally writes those changes back onto the original pages, at which point the launch itself has served its purpose and is normally deleted.

A launch is a private, versioned copy of a content tree where future changes can be reviewed and scheduled before they ever go live.

Launch copy
A full copy of the selected pages under /content/launches, editable without touching the live content
Promote
Merges a launch's edits back onto the original pages, replacing their current content
Live Copy vs Launch
A Live Copy tracks an ongoing relationship between pages; a launch is a one-time staged change set
  • launch
  • staging
  • versioning
  • promote

Replication & Dispatcher

Replication: Author to Publish

Author: editor clicks "Publish" on /content/myco/us/en/products
  -> ReplicationAction ACTIVATE queued on agent "publish"
  -> replication queue (per agent) sends the package to its transport endpoint

Publish instance receives the package
  -> content written into publish's own repository
  -> replication listener fires -> dispatcher flush request issued

Dispatcher (in front of publish)
  -> receives the flush -> invalidates the cached .html for that path
  -> the next visitor request is a fresh cache miss, refilled from publish

Author and publish are separate repositories on purpose — editors need a place to draft and review without exposing half-finished work, so nothing an author does is visible to a visitor until it’s explicitly activated. The replication agent is the only thing bridging the two, which is also why a misconfigured or paused agent is the first thing to check when published content simply isn’t showing up.

The chain doesn’t stop at publish, either: because the dispatcher caches rendered pages in front of publish, replication also has to tell it what just changed, or visitors would keep getting a stale cached copy no matter how successfully the content itself replicated.

Replication is how content moves from the author instance, where it is edited, to the publish instances that actually serve it to visitors.

Replication agent
The author-side configuration, endpoint and transport, that knows where and how to send content
Activate / Deactivate
The two core replication actions — push a page live, or pull a previously published one back
Replication queue
A per-agent queue of pending jobs, retried automatically until the target acknowledges receipt
  • replication
  • replication agent
  • activate
  • publish

The Dispatcher Cache

Publish instances are comparatively expensive to render from — a request has to resolve a resource, run its scripts, and assemble a response. The dispatcher sits in front of that entire process and, for any request it has already served before, just returns the file it saved to disk last time, without publish ever being asked to do the work again.

That efficiency depends entirely on knowing when a cached file stops being correct, which is why flush and invalidate exist as their own step in replication rather than something the dispatcher figures out on its own. A missed or misconfigured flush is the classic cause of a site that “still shows the old content” long after the actual page was republished correctly; filter rules, separately, are what keep the dispatcher from ever forwarding requests to paths that were never meant to be public in the first place.

The dispatcher is AEM's reverse-proxy cache, and its whole job is to serve as many requests as possible without ever reaching a publish instance.

Cached file
A rendered .html response stored on disk, keyed by the request's path
Flush / invalidate
A replication-triggered request telling the dispatcher that a specific cached file is now stale
Auto-invalidate
Invalidating everything under a changed path's ancestor folders too, not just the one exact file
Filter rules
The dispatcher.any rules deciding which request paths are even allowed to reach publish at all
  • dispatcher
  • cache
  • flush
  • invalidate
webcheet

↑↓ navigate↵ openesc close