Master AEM Cheat Sheet — All 27 Syllabus Points in One File
Target: 3–4 years experienced AEM Developer What this is: every "Cheat Sheet" section from files 01–18, pulled into one place, in syllabus order. What this is not: a way to learn these topics for the first time. If a block below doesn't immediately make sense, that's the signal to go read the full file it came from — the number next to each heading tells you which one. Project domain: a global energy technology company's marketing site.
How to use this file
This is the day-before-the-interview document, not the week-one document. Every block here is lifted directly from the corresponding full file's own Cheat Sheet section, so it's already been through the same accuracy pass as the teaching content — nothing here is a new, unverified summary. Read down the list once a week during your prep to see what's gone stale in your memory, and do a full top-to-bottom pass the morning of an interview.
If a code snippet, path, or console URL doesn't ring a bell at all, that's more useful information than the snippet itself — it's telling you exactly which file to reopen.
Syllabus map — all 27 points, at a glance
| Point(s) | Topic | File |
|---|---|---|
| 1 | AEM Architecture | 01 |
| 2 | Component Development | 02 |
| 3 | Editable Templates and Policies | 03 |
| 4, 5 | Clientlibs | 04 |
| 6, 8, 9, 10, 12 | Sling Models | 05 |
| 7, 11, 13 | OSGi and Services | 06 |
| 14, 15, 16 | Servlets | 07 |
| 17, 18 | HTL (Sightly) | 08 |
| 19 (first half) | Workflows | 09 |
| 19 (second half) | Schedulers, Sling Jobs and Events | 10 |
| 20 | Dialog Validation | 11 |
| 21 | MSM and Translation | 12 |
| 22 | Users, Groups and Permissions | 13 |
| 23 | AEM as a Cloud Service | 14 |
| 24, 25 | Content Fragments | 15 |
| 24 (completing) | Experience Fragments | 16 |
| 26 | Sling Model Exporter | 17 |
| 27 | Replication and Distribution | 18 |
01 — AEM Architecture (Syllabus point 1)
Console URLs
/system/console/bundles Bundle list and states
/system/console/components DS components — active or unsatisfied
/system/console/configMgr OSGi configuration in effect
/system/console/services The service registry
/system/console/servletresolver Which servlet handles a given URL
/system/console/status-adapters Registered adapter factories
/system/console/slinglog Logger configuration
/system/console/tracer Sling Log Tracer
/system/console/depfinder Which bundle exports a package
/system/console/jmx MBeans — Oak, queues, sessions
/system/console/healthcheck Health checks
/system/console/slingevent Sling job queues
/system/console/status-Threads Thread dump
/crx/de CRXDE Lite
/crx/packmgr Package Manager
/etc/replication/agents.author Replication agents (6.5)
/libs/cq/search/content/querydebug.html
/libs/granite/operations/content/diagnosistools/queryPerformance.htmlProperties you must recognise instantly
sling:resourceType which component renders this node
sling:resourceSuperType which component this one inherits from
cq:template which template the page was created from
jcr:primaryType the node's type
jcr:title title
cq:allowedTemplates which templates may be used under a path
cq:isContainer this node can hold child components
sling:configRef links a content branch to its /conf config
allowProxy exposes a clientlib under /etc.clientlibs
categories the clientlib's name for inclusion
embed pull another clientlib's code into this one
dependencies require another clientlib to load firstLog locations
crx-quickstart/logs/error.log exceptions
crx-quickstart/logs/request.log response times
crx-quickstart/logs/access.log who requested what
crx-quickstart/logs/stdout.log startup output
<apache>/logs/dispatcher.log cache hit/miss, filter denials
Cloud Service: Cloud Manager → Download Logs, or `aio aem:rde:logs`Maven commands
mvn clean install -PautoInstallSinglePackage everything → author 4502
mvn clean install -PautoInstallSinglePackagePublish everything → publish 4503
mvn clean install -PautoInstallBundle -pl core bundle only, fast
mvn clean install -PautoInstallPackage -pl ui.apps ui.apps onlyRepoinit syntax
create service user my-service with path system/cq:services/myproject
set ACL for my-service
allow jcr:read on /content
allow jcr:read,jcr:write on /var/myproject
end
create path (sling:Folder) /var/myproject
create group myproject-authors
add myproject-authors to group contributor02 — Component Development (Syllabus point 2)
Component folder
/apps/<project>/components/<name>/
.content.xml cq:Component + jcr:title + componentGroup [+ sling:resourceSuperType]
<name>.html HTL — must match the folder name
_cq_dialog/.content.xml
_cq_editConfig.xml
_cq_template/.content.xml
clientlibs/.content.xml properties
jcr:primaryType="cq:Component" makes it a component
jcr:title name shown to authors
jcr:description tooltip
componentGroup browser group; ".hidden" hides it
sling:resourceSuperType inherit from another component
cq:isContainer="{Boolean}true" can hold child components
cq:noDecoration="{Boolean}true" no wrapper divDialog skeleton
cq:dialog (sling:resourceType="cq/gui/components/authoring/dialog")
└ content → granite/ui/components/coral/foundation/container
└ items
└ tabs → .../foundation/tabs
└ items
└ <tabname> (jcr:title) → .../foundation/container
└ items
└ columns → .../foundation/fixedcolumns
└ items
└ column → .../foundation/container
└ items
└ <your fields>Field resource types (prefix granite/ui/components/coral/foundation/form/)
textfield · textarea · pathfield · select · checkbox · switch
numberfield · datepicker · radiogroup · hidden · fileupload
multifield · colorfield
cq/gui/components/authoring/dialog/richtext rich text
cq/gui/components/coral/common/form/tagfield tag pickerField properties
name="./property" ALWAYS the ./ prefix
fieldLabel the label
fieldDescription help text
required="{Boolean}true" mandatory
emptyText placeholder
value / uncheckedValue checkboxes — set BOTH
rootPath restrict a pathfield
composite="{Boolean}true" composite multifield
selected="{Boolean}true" default option in a selectResource Merger
sling:orderBefore="fieldName" position a merged field
sling:hideResource="{Boolean}true" remove an inherited field
sling:hideProperties="[prop1,prop2]" hide inherited propertiescq:editConfig
cq:actions="[text:Label,-,edit,copymove,delete,insert]"
cq:dialogMode="floating|inline|auto"
cq:listeners → afteredit / afterinsert = "REFRESH_PAGE" | "REFRESH_SELF"
cq:dropTargets → accept, groups, propertyName
cq:inplaceEditing → editorType="text|title|plaintext"HTL essentials
data-sly-use.model="com.x.Model" bind a Sling Model
data-sly-test="${model.ready}" conditional render
data-sly-list.item="${model.items}" loop
data-sly-element="${model.headingLevel}" dynamic tag name
data-sly-resource="${path @ resourceType='...', decoration=false}"
${value @ context='html'} rich text
${value @ context='uri'} URLs
${value @ context='unsafe'} ONLY for serialiser-produced JSON-LDAccessibility contract for an accordion
<button aria-expanded="false" aria-controls="panelId" id="buttonId">
<panel id="panelId" role="region" aria-labelledby="buttonId" hidden>
decorative icons → aria-hidden="true"
dynamic updates → <div role="status" aria-live="polite">03 — Editable Templates and Policies (Syllabus point 3)
Paths
/conf/<project>/settings/wcm/templates/<name>/ the template
├── jcr:content status, allowedPaths, jcr:title
├── structure/ every page, LIVE
├── initial/ new pages only, copied once
└── policies/ cq:policy mapping (mirrors structure)
/conf/<project>/settings/wcm/policies/ policy DEFINITIONS
/conf/<project>/settings/wcm/template-types/ template blueprints
/apps/<project>/templates/ static templates (legacy)
/etc/designs/ legacy design configsTemplate properties (jcr:content)
jcr:title name shown to authors
status draft | enabled | disabled
allowedPaths regex array, e.g. [/content/energy(/.*)?]
ranking order in the Create Page listStructure properties
sling:resourceType the page component
cq:template self-reference to the template
editable="{Boolean}true" UNLOCK a structure component
cq:responsive responsive grid breakpointsPolicy properties
cq:policy on a mapping node → points at a definition
components allowed list: ["group:My Group", "proj/components/x"]
cq:styleGroups Style System class definitions
clientlibs on a PAGE policy → categories to load
jcr:title name it properly, not policy_1584032Content-side properties
cq:template on a page's jcr:content → its template
cq:allowedTemplates on a folder's jcr:content → permitted templates below
sling:configRef which /conf folder this branch resolves config fromPackage filter (ui.content)
<filter root="/conf/energy" mode="merge"/>
<filter root="/content/energy" mode="merge"/>Diagnostic checklist — template not appearing
1. status = enabled?
2. allowedPaths regex matches the creation path?
3. cq:allowedTemplates on the content folder?
4. author has read on /conf?Diagnostic checklist — can't add a component
1. In the container policy's components list (directly or by group)?
2. componentGroup empty or .hidden?
3. Nested container with NO cq:policy mapped?
4. Author has modify permission on the path?04 — Clientlibs (Syllabus points 4, 5)
Folder structure
/apps/<project>/clientlibs/clientlib-site/
.content.xml cq:ClientLibraryFolder + config
css.txt which CSS files, in order
js.txt which JS files, in order
css/ LESS/CSS source
js/ JS source
resources/ served AS-IS: fonts, images.content.xml properties
jcr:primaryType="cq:ClientLibraryFolder"
categories="[energy.product]" the logical name(s)
allowProxy="{Boolean}true" servable via /etc.clientlibs
dependencies="[energy.base,jquery]" load first, SEPARATE files
embed="[energy.component.cta]" pull INSIDE, ONE filecss.txt / js.txt
#base=css set the source folder
# comment
variables.less one file per line
base.less IN LOAD ORDER
components.lessProxy path translation
/apps/energy/clientlibs/clientlib-site
↓ strip /apps
/etc.clientlibs/energy/clientlibs/clientlib-site.css
/etc.clientlibs/energy/clientlibs/clientlib-site/resources/fonts/x.woff2Including in HTL
<sly data-sly-use.clientlib="core/wcm/components/commons/v1/templates/clientlib.html"/>
<sly data-sly-call="${clientlib.css @ categories='energy.site'}"/> in <head>
<sly data-sly-call="${clientlib.js @ categories='energy.site'}"/> end of <body>
<sly data-sly-call="${clientlib.all @ categories='energy.site'}"/> both
@ async=true don't block, order not guaranteed
@ defer=true run after parsing
@ media='print' non-blocking print stylesGranite alternative
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"/>Legacy JSP
<cq:includeClientLib categories="energy.site"/>Debug URLs
?debugClientLibs=true unminified, individual files
/libs/granite/ui/content/dumplibs.html all clientlibs
/libs/granite/ui/content/dumplibs.test.html?categories=X what X resolves to
/libs/granite/ui/content/dumplibs.validate.html duplicate categories
/libs/granite/ui/content/dumplibs.rebuild.html force rebuildDispatcher rule
/0100 { /type "allow" /path "/etc.clientlibs/*"
/extension '(css|js|png|jpg|svg|woff|woff2)' }OSGi config — HTML Library Manager
com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl
debug · minify · gzip · timing05 — Sling Models (Syllabus points 6, 8, 9, 10, 12)
The @Model annotation
@Model(
adaptables = Resource.class, // or SlingHttpServletRequest.class, or both
adapters = Cta.class, // register as an interface
resourceType = "energy/components/cta", // bind to a resource type
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
cache = true // reuse the instance per adaptable
)Injector-specific annotations
@ValueMapValue property on THIS node
@ChildResource child node -- Resource, model, or List<Model>
@ResourcePath resolve a path property into a Resource
@OSGiService an OSGi service (NOT @Reference)
@ScriptVariable HTL binding -- REQUIRES request adaptable
@RequestAttribute set upstream by a filter
@SlingObject resourceResolver, resource, request, response
@Self the adaptable itselfModifiers
@Named("jcr:title") property name != field name
@Default(values = "arrow") fallback value
@Optional / @Required per-field injection strategy
@Via("resource") change what it resolves against
@OSGiService(filter = "(...)") pick one implementationLifecycle
@PostConstruct
protected void init() { } // void, NO arguments, after injection@ScriptVariable bindings (request-adaptable only)
currentPage the page being rendered
currentStyle the RESOLVED POLICY (file 03)
pageManager look up pages by path
wcmmode edit / preview / disabled
currentDesign legacy design
resourceResolverUsing a model
<sly data-sly-use.cta="com.energy.core.models.Cta"/>
${cta.linkText} → calls getLinkText()
${cta.ready} → calls isReady()CtaModel m = resource.adaptTo(CtaModel.class);
if (m != null) { ... } // ALWAYSBundle header
Sling-Model-Packages: com.energy.core.modelsDebugging
/system/console/status-adapters is the model registered?
/system/console/tracer trace one request
error.log @PostConstruct exceptions
${model} in HTL did adaptation succeed?Test setup
context.load().json("/faq/content.json", "/content");
context.addModelsForClasses(FaqModel.class, FaqItemModel.class); // BOTH
context.registerService(MyService.class, Mockito.mock(MyService.class));06 — OSGi and Services (Syllabus points 7, 11, 13)
Declaring
@Component // component, NOT a service
@Component(service = MyService.class) // component AND service
@Component(service = {A.class, B.class}) // several interfaces
@Component(immediate = true) // activate without a consumer
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
@Component(property = {"service.ranking:Integer=100"})
@Component(scope = ServiceScope.SINGLETON) // defaultLifecycle
@Activate protected void activate(MyConfig config) { }
@Modified // usually the SAME method as @Activate
@Deactivate protected void deactivate() { } // RELEASE RESOURCESInjection
@Reference
private MyService service; // MANDATORY by default
@Reference(cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC)
private volatile MyService optional; // volatile REQUIRED
@Reference(cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC)
private volatile List<Handler> handlers;
@Reference(target = "(component.name=com.energy.CachedImpl)")
private MyService specific;Configuration
@ObjectClassDefinition(name = "My Service")
public @interface MyConfig {
@AttributeDefinition(name = "Endpoint")
String endpoint() default "https://api/v1";
@AttributeDefinition(name = "Timeout (ms)")
int timeout() default 3000;
}
@Component(service = MyService.class)
@Designate(ocd = MyConfig.class) // add factory = true for many instances
public class MyServiceImpl implements MyService { }Config file
ui.config/.../apps/energy/osgiconfig/config.publish.prod/
com.energy.core.services.impl.MyServiceImpl.cfg.json ← PID = class name
factory instance:
com.energy...MyServiceImpl~products.cfg.jsonSling registration
@Component(service = Servlet.class)
@SlingServletResourceTypes(resourceTypes = "...", selectors = "...",
extensions = "html", methods = "GET")
@Component(service = Servlet.class)
@SlingServletPaths("/bin/energy/export")
@Component(service = Filter.class)
@SlingServletFilter(scope = SlingServletFilterScope.REQUEST)Consoles
/system/console/bundles bundle state, unsatisfied imports
/system/console/components component state + WHICH REFERENCE IS MISSING
/system/console/configMgr effective configuration values
/system/console/services what's registered, and ranking
/system/console/slinglog package-scoped DEBUG loggerTesting
service = context.registerInjectActivateService(new MyServiceImpl(), configMap);07 — Servlets (Syllabus points 14, 15, 16)
Registration
@Component(service = Servlet.class)
@SlingServletResourceTypes(
resourceTypes = "energy/components/listing",
selectors = "cards",
extensions = "html",
methods = HttpConstants.METHOD_GET
)
@Component(service = Servlet.class)
@SlingServletPaths("/bin/energy/quote") // + authorisation + dispatcher ruleOld property style
sling.servlet.resourceTypes · sling.servlet.selectors
sling.servlet.extensions · sling.servlet.methods
sling.servlet.paths · sling.servlet.prefixBase classes
SlingSafeMethodsServlet GET HEAD OPTIONS TRACE (read-only)
SlingAllMethodsServlet + POST PUT DELETE (writes)Methods
protected void doGet (SlingHttpServletRequest req, SlingHttpServletResponse res)
protected void doPost (...)
protected void doPut (...)
protected void doDelete(...)Reading the request
request.getResource() the resolved resource
request.getResourceResolver() the USER's resolver — do NOT close
request.getRequestPathInfo().getSelectors() ["cards"]
request.getRequestPathInfo().getSuffix() "/2" — NULL if absent
request.getRequestPathInfo().getExtension() "html"
request.getParameter("q") query/form parameterWriting the response
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(SlingHttpServletResponse.SC_CREATED);
new ObjectMapper().writeValue(response.getWriter(), payload);Status codes
200 OK 201 Created 202 Accepted 204 No Content
400 Bad Request 403 Forbidden 404 Not Found 405 Method Not Allowed
500 Internal Server ErrorFilter
@Component(service = Filter.class)
@SlingServletFilter(scope = SlingServletFilterScope.REQUEST)
@ServiceRanking(-700)Scopes: REQUEST · INCLUDE · COMPONENT · FORWARD · ERROR
Dispatcher rule for a path servlet
/0200 { /type "allow" /path "/bin/energy/quote" /method "POST" }Debugging
/system/console/servletresolver which servlet handles this URL
/system/console/components Active or Unsatisfied
404 = not matched · 405 = matched, wrong method08 — HTL (Sightly) (Syllabus points 17, 18)
Calling a model
<sly data-sly-use.cta="com.energy.core.models.CtaModel"/>
${cta.linkText} → getLinkText()
${cta.ready} → isReady()
<!-- with parameters -->
<sly data-sly-use.m="${'com.x.Model' @ rootPath='/content/x', max=12}"/>All block elements
data-sly-use.name="..." instantiate
data-sly-set.name="${...}" define a variable
data-sly-test="${...}" conditional
data-sly-test.name="${...}" conditional + store
data-sly-list.item="${...}" repeat CHILDREN
data-sly-repeat.item="${...}" repeat the ELEMENT
data-sly-unwrap drop the element, keep content
data-sly-resource="${...}" include a RESOURCE
data-sly-include="file.html" include a SCRIPT
data-sly-template.name="${@ a}" define reusable markup
data-sly-call="${name @ a=x}" call it
data-sly-element="${...}" set the tag name
data-sly-attribute.href="${...}" set an attribute
data-sly-text="${...}" replace content
<sly> invisible elementLoop variables (for data-sly-list.item)
itemList.index 0-based
itemList.count 1-based
itemList.first itemList.last itemList.middle
itemList.odd itemList.evendata-sly-resource options
@ resourceType='energy/components/cta'
@ decoration=false
@ decorationTagName='li'
@ addSelectors='teaser'Expression options
${v @ context='html'} rich text
${v @ context='uri'} URLs
${v @ context='unsafe'} NO escaping -- justify it
${'{0} of {1}' @ format=[a, b]} build a string
${arr @ join=', '} join an array
${'Label' @ i18n, hint='where it appears'} translate
${date @ format='dd MMM yyyy'} format a dateContexts
text · html · attribute · uri · scriptString
styleString · elementName · number · unsafeGlobals
properties · pageProperties · inheritedPageProperties
currentPage · currentStyle (the POLICY) · resource
resourceResolver · wcmmode · component · requestPatterns
<!-- if / else -->
<sly data-sly-test.hasX="${m.x}">A</sly>
<sly data-sly-test="${!hasX}">B</sly>
<!-- default -->
<sly data-sly-set.t="${m.title || 'Fallback'}"/>
<!-- authoring placeholder -->
<sly data-sly-test="${!m.ready && wcmmode.edit}"
data-sly-resource="${'' @ resourceType='wcm/core/components/placeholder'}"/>
<!-- colon property -->
${properties['jcr:title']}
<!-- comments -->
<!--/* stripped */--> <!-- sent to the browser -->09 — Workflows (Syllabus point 19, first half)
Paths
/conf/global/settings/workflow/models/ model DESIGN
/var/workflow/models/ model RUNTIME (sync!)
/conf/global/settings/workflow/launcher/config/ launchers
/var/workflow/instances/ running + completed instancesCustom process step
@Component(service = WorkflowProcess.class,
property = {"process.label=Energy - My Step"})
public class MyProcess implements WorkflowProcess {
@Override
public void execute(WorkItem workItem,
WorkflowSession session,
MetaDataMap metaData) throws WorkflowException { }
}Inside execute
String path = workItem.getWorkflowData().getPayload().toString();
String type = workItem.getWorkflowData().getPayloadType(); // JCR_PATH | JCR_UUID
String args = metaData.get("PROCESS_ARGS", String.class);
ResourceResolver r = session.adaptTo(ResourceResolver.class); // do NOT close
// metadata shared across steps
workItem.getWorkflow().getWorkflowData().getMetaDataMap().put(k, v);Dynamic participant
@Component(service = ParticipantStepChooser.class,
property = {"chooser.label=Energy - Route by Product Line"})
public class MyChooser implements ParticipantStepChooser {
public String getParticipant(...) { return "a-group-name"; }
}Launcher properties
glob = "/content/energy/.../products/(.*)" SCOPE IT
nodetype = "cq:PageContent" not cq:Page
eventType = Created | Modified | Removed
runModes = "[author]" RESTRICT IT
condition = optional expression
workflow = /var/workflow/models/...
enabled = trueConsoles
Tools → Workflow → Models definitions, and SYNC
Tools → Workflow → Instances what's running, where it's stuck
Tools → Workflow → Failures steps that threw
Tools → Workflow → Launchers why it did/didn't start
/system/console/components is the process step even activeTool choice
Time-based, no human → Scheduler
Bulk async, guaranteed → Sling Job
Human step or audit trail → Workflow10 — Schedulers, Sling Jobs and Events (Syllabus point 19, second half)
Scheduler
@Component(service = Runnable.class, immediate = true)
@Designate(ocd = MyConfig.class)
public class MyTask implements Runnable {
public void run() { }
}
// properties
scheduler.expression = "0 0 2 * * ?"
scheduler.concurrent = false // almost always
scheduler.runOn = "SINGLE" // SINGLE | LEADER | ALL
scheduler.period = 3600 // alternative to cron, in secondsCron (Quartz — starts with SECONDS)
sec min hour day-of-month month day-of-week [year]
0 0 2 * * ? 2am daily
0 */15 * * * ? every 15 minutes
0 0 */4 * * ? every 4 hours
0 0 3 ? * MON 3am Mondays
0 0 0 1 * ? midnight, 1st of the month
0 30 6 ? * MON-FRI 6:30am weekdays
ONE day field must be ? (not both *)Sling Job — produce
@Reference private JobManager jobManager;
Map<String, Object> props = new HashMap<>();
props.put("productId", "TX-4000");
jobManager.addJob("energy/product/sync", props);Sling Job — consume
@Component(service = JobConsumer.class,
property = {JobConsumer.PROPERTY_TOPICS + "=energy/product/sync"})
public class MyConsumer implements JobConsumer {
public JobResult process(Job job) {
String id = job.getProperty("productId", String.class);
return JobResult.OK; // FAILED = retry | CANCEL = don't
}
}ResourceChangeListener
@Component(service = ResourceChangeListener.class,
property = {
ResourceChangeListener.PATHS + "=/content/energy/products",
ResourceChangeListener.CHANGES + "=ADDED",
ResourceChangeListener.CHANGES + "=CHANGED"
})
public class MyListener implements ResourceChangeListener {
public void onChange(List<ResourceChange> changes) {
// CHEAP CHECK ONLY, then dispatch a job
}
}EventHandler
@Component(service = EventHandler.class,
property = {EventConstants.EVENT_TOPIC + "=" + ReplicationAction.EVENT_TOPIC})
public class MyHandler implements EventHandler {
public void handleEvent(Event event) {
ReplicationAction a = ReplicationAction.fromEvent(event);
}
}Storage
/var/eventing/jobs Sling Jobs
/var/workflow/instances Workflow instances (file 09)
schedulers IN MEMORY -- nothing persistedConsoles
/system/console/scheduler registered tasks + next fire time
/system/console/slingevent job queues, topics, failures
/system/console/components Active or UnsatisfiedThe choice
Clock → Scheduler
Repository change → Event handler (then dispatch a job)
Code decides, must not be lost → Sling Job
A human must act → Workflow11 — Dialog Validation (Syllabus point 20)
Register a validator
(function ($, $document) {
"use strict";
$.validator.register({
selector: "[data-validation='energy.productcode']",
validate: function (el) {
var v = el.val();
if (!v) { return; } // empty = required's job
if (!/^[A-Z]{2}-\d{4}$/.test(v)) {
return "Must be like TX-4000"; // STRING = invalid
}
// nothing returned = valid
}
});
})(jQuery, jQuery(document));Clientlib
jcr:primaryType="cq:ClientLibraryFolder"
categories="[cq.authoring.dialog]"Dialog field
validation="energy.productcode" → renders data-validation="..."
required="{Boolean}true"
fieldDescription="Format: TX-4000" → prevents, don't just catch
<granite:data jcr:primaryType="nt:unstructured" maxLength="60"/>
→ renders data-max-length, read as $(el).data("maxLength")Cross-field revalidation
$document.on("change", "[name='./startDate']", function () {
$(this).closest("form").find("[name='./endDate']").trigger("change");
});Show/hide (built in)
select: granite:class="cq-dialog-dropdown-showhide"
<granite:data cq-dialog-dropdown-showhide-target=".my-target"/>
group: granite:class="hide my-target"
<granite:data showhidetargetvalue="optionValue"/>
checkbox equivalent: cq-dialog-checkbox-showhideServer-side counterpart
@PostConstruct
protected void init() {
this.valid = PATTERN.matcher(value).matches();
if (value != null && !valid) {
LOG.warn("Invalid value '{}' at {} — dialog validation bypassed",
value, resource.getPath());
}
}
public boolean isReady() { return valid; }Debug
network tab (dialog open) did the clientlib load
inspect the field is data-validation rendered
console, ABOVE where you're looking earlier JS error
jQuery("[data-validation='x']").length does the selector match12 — MSM and Translation (Syllabus point 21)
Structure
/content/energy/
├── language-masters/
│ ├── en authored SOURCE
│ ├── de language copy of en (translated)
│ └── fr language copy of en (translated)
├── us/en live copy of language-masters/en
├── de/de live copy of language-masters/de
└── ch/
├── de live copy of language-masters/de
└── fr live copy of language-masters/frLive copy relationship
<page>/jcr:content/cq:LiveSyncConfig
cq:master = "/content/energy/language-masters/de"
cq:isDeep = true
cq:rolloutConfigs = ["/libs/msm/wcm/rolloutconfigs/default"]Cancellation
cq:propertyInheritanceCancelled = ["contactPhone","contactEmail"]
← PREFER THIS SCOPE
page level: Suspend (resumable) | Detach (permanent)Rollout config
cq:trigger = "rollout" | "onModify" | "onActivate" | "onDeactivate"
actions:
versionCopy rollback point
contentCopy new pages
contentUpdate changed content
contentDelete removals
referencesUpdate REWRITE INTERNAL LINKS ← don't omit
orderChildren page order
pageMove moved pages
/libs/msm/wcm/rolloutconfigs/ out of the box
/apps/msm/wcm/rolloutconfigs/ customTranslation
TIF config: /conf/<site>/settings/cloudconfigs/translation
Language root: page named en | de | pt_br, plus jcr:language
Create Language Copy → structure + translation project
Update Language Copy → only what CHANGED (cost)
Machine: news, blogs · Human: specs, legal, regulatoryThe three steps
1. TRANSLATE → into the language master
2. ROLL OUT → to country live copies (AUTHOR only)
3. ACTIVATE → to publishJava API
@Reference LiveRelationshipManager liveRelationshipManager;
liveRelationshipManager.hasLiveRelationship(resource);
liveRelationshipManager.getLiveRelationship(resource, false);
→ relationship.getSourcePath()
→ relationship.getStatus().isCancelled()Audit query
path=/content/energy
property=cq:propertyInheritanceCancelled
property.operation=existsDebug order for "the rollout didn't work"
1. Inheritance cancelled? (most common, and NOT an error)
2. Was it ACTIVATED? (second most common)
3. Right sync action?
4. cq:isDeep true?
5. Dispatcher cache13 — Users, Groups and Permissions (Syllabus point 22)
Where things live
/home/users/<bucket>/<userid> rep:User
/home/groups/<bucket>/<groupid> rep:Group
/home/users/system/... service users
<node>/rep:policy the ACL (rep:GrantACE / rep:DenyACE)Privileges
jcr:read
jcr:modifyProperties · jcr:addChildNodes
jcr:removeNode · jcr:removeChildNodes
jcr:readAccessControl · jcr:modifyAccessControl
jcr:versionManagement · jcr:lockManagement
crx:replicate ← AEM: activate / deactivate
rep:write ← aggregate of the 4 write privileges
jcr:all ← everythingEvaluation
1. DENY BY DEFAULT
2. CLOSEST ACL WINS (deeper overrides ancestor)
3. DENY BEATS ALLOW (at the same level)
→ You CANNOT grant around a deny by adding another group.rep:glob
(absent) node + everything below
"" THE NODE ONLY
* everything below
*/jcr:content* page content nodesRepoinit
create service user energy-content-reader with path system/cq:services/energy
create group energy-de-authors
add energy-de-authors to group energy-authors
set ACL for energy-de-authors
allow jcr:read,rep:write on /content/energy/de
allow jcr:read on /content/energy/language-masters
allow jcr:read on /content/energy/shared restriction(rep:glob,"")
deny crx:replicate on /content/energy/de
end
create path (sling:Folder) /var/energy/reportsService user mapping
{ "user.mapping": [
"com.energy.core:content-reader=[energy-content-reader]"
]}Format: bundle-symbolic-name:subservice=[system-user] — all three must match.
Getting a resolver
Map<String,Object> p = Collections.singletonMap(
ResourceResolverFactory.SUBSERVICE, "content-reader");
try (ResourceResolver r = factory.getServiceResourceResolver(p)) {
// runs as the SERVICE USER
} // CLOSE what you opened
request.getResourceResolver(); // current user -- do NOT closeStandard groups
administrators · contributors · dam-users
workflow-users · user-administrators
everyone ← EVERY user implicitly, including anonymousDebug
Tools → Security → Users → IMPERSONATE ← the best tool
CRXDE → Access Control tab effective policy, incl. inherited
/home/users/system does the service user exist
startup logs Repoinit failures
private window → publish directly anonymous, no dispatcher14 — AEM as a Cloud Service (Syllabus point 23)
The frame
6.5 = a SERVER you own → you can change it, and you maintain it
AEMaaCS = a SERVICE you use → immutable, disposable, Adobe maintains itBenefits (with the reason)
No version upgrades recurring cost that produces no features
Publish auto-scales stop provisioning for peak
No repo maintenance compaction, GC, indexes — invisible until a disk fills
Asset Compute ingestion stops competing with authors
Pipeline discipline production can't drift from Git
Adobe CDN included one less contract and configurationDownsides — say these
NO emergency hotfix path
No CRXDE on stage / prod
No custom run modes, no admin session, no local disk
Adobe's release cadence
Migration is real work
Cost is different, not automatically lowerDeveloper changes
/apps + /libs IMMUTABLE at runtime
Service users, ACLs REPOINIT only
Packages ui.apps (immutable) vs ui.content + ui.config (mutable)
Run modes author|publish × dev|stage|prod — FIXED
Local disk ephemeral
Long-running work discouraged
@Deactivate matters more — pods recycle constantly
Schedulers pod count VARIES on publishNode stores
AUTHOR → DocumentNodeStore (MongoDB) clustered, shares a repository
PUBLISH → SegmentNodeStore own copy per pod, fasterPipelines
full-stack everything slowest
front-end FE build output
web-tier DISPATCHER config fast
config certain settings fast — the closest thing to a quick fixTooling
Cloud Manager pipelines, environments, logs
AEM SDK local quickstart + dispatcher tools
RDE deploy in SECONDS, dev iteration only
Developer Console status, bundles, OSGi configs
aio CLI LIVE LOG TAILING, RDE deploysMigration tools, in order
Best Practices Analyzer → Cloud Acceleration Manager
→ Repository Modernizer → Dispatcher Converter
→ Asset Workflow Migration → Content Transfer Tool15 — Content Fragments (Syllabus points 24, 25)
Paths
/conf/<project>/settings/dam/cfm/models/<model> the MODEL
/content/dam/<...>/fragments/<fragment> the FRAGMENT (an asset)
jcr:content/data/
cq:model = <path to the model>
master/ ← default variation
<variation>/ ← othersReading in a model
Resource r = resourceResolver.getResource(fragmentPath); // null-check
ContentFragment fragment = r.adaptTo(ContentFragment.class); // null-check
// ↑ the ASSET node, not jcr:content
ContentElement element = fragment.getElement("productName");
String value = element.getContent();
FragmentData data = element.getValue();
String contentType = data.getContentType(); // text/html | text/plain
String typed = data.getValue(String.class);
ContentVariation v = element.getVariation("social"); // null → use master
Iterator<ContentElement> all = fragment.getElements();
fragment.getName() · getTitle() · getDescription()Do it in @PostConstruct, not a getter.
Content types
text/plain → default HTL escaping
text/html → ${value @ context='html'}
text/x-markdown → render as markdownGraphQL
Ad-hoc: POST to the endpoint NOT cacheable
Persisted: GET /graphql/execute.json/<config>/<queryName>;var=value
CACHED as a file
Persisted queries stored under /conf/<project>/settings/graphqlModel changes
ADD field → safe
RENAME field → BREAKS existing fragments
REMOVE field → data stays, stops surfacing
CHANGE type → riskyDebug order — "blank on publish"
1. Was the FRAGMENT activated? ← publishing the page does NOT
2. Can anonymous read the DAM path?
3. Is the path the same on both environments?16 — Experience Fragments (Syllabus point 24, completing it)
Structure
/content/experience-fragments/energy/de/de/site/header/
master ← a VARIATION (a cq:Page) -- reference THIS
social ← another variation, possibly a different templatePlacement in a template structure
<header sling:resourceType="energy/components/experiencefragment"
fragmentVariationPath=".../site/header/master"/>
<!-- no editable=true → LOCKED -->Plain HTML export
/content/experience-fragments/.../master.plain.html
→ just the markup, no page chrome
→ a normal cacheable GET
→ a PUBLIC URLCF vs XF
CF: dam:Asset · /content/dam · from a MODEL · JSON · CHANNELS
XF: cq:Page · /content/experience-fragments · from a TEMPLATE · HTML · PAGES
"Content without presentation" vs "Presentation with content"Localisation
/content/experience-fragments/energy/
language-masters/en|de/site/header/master ← authored + translated
de/de/site/header/master ← LIVE COPY
ch/de/site/header/master ← LIVE COPYDebug
Missing on publish → the FRAGMENT wasn't activated
Old version showing → rendered inline; the PAGE cache holds it
Unstyled in the editor → XF template's page policy lacks the clientlibs
Won't resolve localised → folder structure doesn't match the locale
Who uses it? → the References panel17 — Sling Model Exporter (Syllabus point 26)
Making a model exportable
@Model(
adaptables = SlingHttpServletRequest.class,
adapters = { MyModel.class, ComponentExporter.class },
resourceType = "energy/components/cta", // ← REQUIRED
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, // "jackson"
extensions = ExporterConstants.SLING_MODEL_EXTENSION) // "json"
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CtaImpl implements Cta, ComponentExporter { }The URL
/content/.../cta.model.json a component
/content/.../page.model.json a whole page (SPA)
selector = model · extension = json → PATH-BASED → CACHEABLEControlling the output
@JsonProperty("url") name the field EXPLICITLY (contract safety)
@JsonIgnore exclude — and skip the getter call
@JsonInclude(NON_NULL) omit nullsSPA interfaces
ComponentExporter → getExportedType() → ":type"
ContainerExporter → getExportedItems() → ":items"
→ getExportedItemsOrder() → ":itemsOrder"SPA binding
MapTo('energy/components/cta')(CtaComponent);Dispatcher
/0110 { /type "allow" /selectors "model" /extension "json" /path "/content/*" }Debug
JSON has jcr:primaryType? → the DefaultGetServlet answered
→ missing resourceType on @Model
404 on publish only → dispatcher selector not allowed
Field missing → @JsonIgnore, or null + NON_NULL
Field appeared → someone added a public getterThe choice
Consumer mirrors our page → Model Exporter (page-shaped)
Consumer has its own structure → Content Fragments + GraphQL
Aggregation / HTML / bespoke → custom servlet18 — Replication and Distribution (Syllabus point 27)
Actions
ACTIVATE → publish
DEACTIVATE → remove from publish, KEEP on author
DELETE → remove from BOTH
TEST → connectivity onlyAgents
/etc/replication/agents.author/
publish author → publish (content)
flush author → dispatcher (CACHE INVALIDATION)
reverse publish → author (polled by author)
Settings tab → enabled, AGENT USER (reads on author), retry, log level
Transport tab → URI /bin/receive, TRANSPORT USER (writes on publish)
Triggers tab → On Modification (leave OFF), On Off TimeThe two users
Agent user reads on AUTHOR → under-privileged = SILENT skipping
Transport user writes on PUBLISH → wrong = 401, QUEUE BLOCKSFrom code
@Reference private Replicator replicator;
Session session = resolver.adaptTo(Session.class);
replicator.replicate(session, ReplicationActionType.ACTIVATE, path);
// user needs crx:replicate
// NEVER in a loop → batched Sling Jobs, out of hours, REFERENCES FIRSTEvents
ReplicationAction.EVENT_TOPIC
ReplicationAction.fromEvent(event).getType() == ACTIVATE
// runs SYNCHRONOUSLY — decide here, dispatch a jobCloud Service
Sling Content Distribution — PUB/SUB
Author publishes to a pipeline; pods SUBSCRIBE and PULL.
A new pod catches up BY ITSELF.
Why: agents push to a CONFIGURED URI; pods are created and
destroyed, so you can't configure an agent for one that
doesn't exist yet — and a new pod has no content.THE SIX-STEP WALK
1. Was it ACTIVATED? page properties → replication status
2. Is the QUEUE blocked? one stuck item blocks the rest
3. Node on PUBLISH? private window, publish DIRECTLY
4. REFERENCES published? fragments, assets, XFs ← the silent one
5. DISPATCHER cached? flush didn't happen / didn't arrive
6. CDN cached? one layer further out
Most common: 1 and 5.Cross-topic threads worth remembering
These recur across multiple files above, and interviewers notice when a candidate connects them instead of treating each file as isolated.
"Works on author, blank on publish." One root cause, over and over: author is authenticated, publish is anonymous. If code relies on a session privilege author has and publish doesn't — or on content that was never activated (files 15, 16, 18) — this is the symptom. The debug order is always: was it activated? → can anonymous actually read it? → is the path the same on both environments?
"A dot is cached, a question mark is not." Selectors and extensions (.model.json, .cards.html) are part of the path, so the dispatcher can cache them. Query parameters (?page=2) are not part of the cache key by default, so they bypass the cache — this shows up in files 04, 07, 15, and 17 wherever a URL is being designed for cacheability.
Derived content needs its own invalidation. A page that renders a Content Fragment, an Experience Fragment, or another page's data (files 02, 15, 16, 17, 18) is only as fresh as the last time the thing driving it was activated and its cache invalidated — activating the source doesn't automatically flush every page that happens to reference it.
Deny wins, closest ACL wins, and you cannot grant your way around a deny (file 13) — this single evaluation rule explains almost every "why can't this user see that" ticket.
On Cloud Service, there is no emergency hotfix path (file 14) — every change, including a genuine production emergency, goes through the same pipeline. The honest mitigation is feature flags and the fast config/web-tier pipelines, not a workaround.
Master Cheat Sheet — AEM Interview Preparation repository. Companion to files 01–18. Teaching style, energy-sector project domain.