How Sling Resolves a Request to an AEM Component
How Sling turns a URL into a Resource and then into an AEM component through sling:resourceType, the /apps and /libs search paths, and why sling:resourceSuperType is the correct pattern for extending Core Components.
When AEM renders a page, what actually happens underneath is a two-step
Sling resolution: first a URL becomes a Resource, then that Resource
becomes a component — an HTL script or a servlet — that decides what HTML
comes back. Understanding this pipeline explains why /apps can override
/libs, why Core Components are extended with sling:resourceSuperType
instead of being overwritten directly, and why a stray selector can break
Dispatcher caching.
From URL to Resource
When a request like /content/mysite/en/home.html arrives, Sling splits
the path into resource path, selectors, and extension, and uses the
ResourceResolver to map /content/mysite/en/home to an actual JCR node.
In an AEM project that node is usually a page (cq:Page), but what
actually matters for rendering is its jcr:content node (or a child
component’s), because that’s where the sling:resourceType property
lives.
From Resource to component: sling:resourceType
sling:resourceType isn’t an absolute path — it’s a relative path Sling
looks up across an ordered list of search paths. For example, if a
component has:
sling:resourceType = "myproject/components/hero"
Sling looks for a script (.html, compiled from HTL to Java under the
hood) or a servlet whose path, relative to one of the search paths,
matches myproject/components/hero.
Sling’s search paths: why /apps comes before /libs
Sling’s default configuration (resource.resolver.searchpath) is
["/apps", "/libs"]. That means for the same sling:resourceType, Sling
always tries whatever script it finds under /apps before the one it
finds under /libs. This is the technical mechanism behind AEM’s classic
“overlay”: if a product resource lives at /libs/something/component and
you place a script at /apps/something/component, yours wins.
On AEM as a Cloud Service, /libs is fully immutable — you will never
deploy anything there. This search-path overlay is still real for certain
platform resources that still live under /libs (parts of Granite UI or
the OSGi console, for instance), but it’s not the mechanism you should
use to extend AEM Core Components — and that’s a common mistake.
Why Core Components aren’t overlaid: sling:resourceSuperType
The AEM Core Components (core/wcm/components/...) are shipped as a
project dependency and end up installed under /apps, not /libs. Since
they’re already in /apps, you can’t “beat” them by placing something
under the same sling:resourceType in /apps without completely
replacing the product component — losing any future improvement Adobe
ships.
The correct pattern is component inheritance via
sling:resourceSuperType:
<!-- /apps/myproject/components/text/.content.xml -->
<jcr:root
jcr:primaryType="cq:Component"
jcr:title="Text"
sling:resourceSuperType="core/wcm/components/text/v2/text"/>
With this, your myproject/components/text component inherits everything
you don’t explicitly override (dialog, HTL script, Sling Model) from the
resourceSuperType. If your HTL script doesn’t define a given block,
Sling walks up the resourceSuperType chain until it finds it on the
Core Component. This is what lets you customize only the 10% you need to
change without duplicating the other 90%.
Selectors and extension also change which script is picked
Selectors and the request extension (.html, .json, a selector like
.print) also factor into resolution: Sling looks for a script specific
to that combination first (text.print.html) before falling back to the
default script (text.html). This is exactly what AEM’s JSON Exporter
(.model.json) relies on to serve a component’s Sling Model
representation without touching the normal render script.
Where this matters on a real AEM project
- Rendering debugging: if a component isn’t picking up your script,
suspect the search path first (is your script actually under
/apps?) and thensling:resourceSuperType(does your component inherit from the rightresourceType, and the right version — e.g.text/v2/text, nottext/v1/text?). - Core Components upgrades: because you extend by inheritance rather than overlay, bumping the Core Components version doesn’t force you to reconcile a full component fork.
- Dispatcher caching: every distinct selector/extension combination your project uses needs to be accounted for in Dispatcher cache rules; a forgotten “debug” selector left in production can generate unnecessary cache entries or, worse, uncacheable content where it should be cacheable.