Everything in AEM lives in the JCR — stored as nodes and properties. Once this clicks, AEM stops being magic.
A folder that can also hold data. Pages, components, configs, images — all nodes.
A name and a value. That's the entire data model of AEM.
Create nodes, edit properties, write files — live on the instance.
# the content tree /apps/aeminsider ├── components ├── clientlibs └── i18n # properties panel jcr:primaryType nt:folder jcr:title AEM Insider
The repository and the codebase are mirrors — code is just content that gets deployed.
Components, rendering templates, client libraries. Deployed from ui.apps.
Pages, assets, everything authors create. From ui.content.
Editable templates, policies, settings. Also from ui.content.
/libs is Adobe's own code. Rule #1 of AEM: never modify /libs. Look, learn, don't touch.
Two ways to build: in CRXDE (instant — today's way) or in code (needs a deployment). A deployment overwrites CRXDE work — export it to code first.
card [cq:Component] ├── jcr:title Card ├── componentGroup AEM Insider │ Site - Content └── card.html
Makes this folder a component — not just a folder.
The name authors see in the editor.
The entry ticket. It comes back later in this video.
Save All. In CRXDE, nothing exists until you hit Save All — every single change.
Authors don't use CRXDE — they need a form. And nobody writes dialogs from scratch: copy a working one, adapt it.
The cq:dialog node from the helloworld component.
Into our card component. Done — card has a dialog.
Fields → ./title and ./description (as a textarea).
cq:dialog jcr:title Card └── content / items / … / items ├── title │ fieldLabel Title │ name ./title └── description [textarea] fieldLabel Description name ./description
name = ./title → saves the value as property "title", directly on the component's node.
Same name on both sides — that's the whole contract. No code in between.
<div class="cmp-card"> <h2>${properties.title}</h2> <p>${properties.description}</p> </div>
./title in the dialog ↔ properties.title in HTL.
HTL escapes output automatically — XSS protection built in, not bolted on.
Real logic lives in Sling Models — later in the series. This component doesn't need one.
"I built my component. Why can't I see it in the editor?" — the #1 question every new AEM developer asks.
Pages are built on editable templates (they live in /conf). Every template decides which components are allowed in its containers.
The layout container's policy lists allowed component groups. Our ticket: AEM Insider Site - Content — the group we set in step one.
Editable templates & policies get their own video later in the series — today we just walk through the door.
New page. Real authoring. The fields we built in CRXDE five minutes ago.
Sites console → Create → Page. Template: Content Page. Title: "Component Test".
Component browser → filter "Card" → drop it into the container.
Open the dialog. Title + description. Save — and it renders.
Develop → Deploy → Allow → Author. Your daily loop as an AEM developer, from today on.
The part most tutorials skip — what actually happened when the author hit save.
# /content/aeminsider/…/component-test # /jcr:content/root/container/card title My First Component description Straight talk on AEM. sling:resourceType aeminsider/ components/card
Content lives in /content. The renderer lives in /apps. That separation is the heart of AEM.
A style block inside the file — scoped to the component class so nothing leaks into the page.
<style> .cmp-card { padding: 24px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,.08); } .cmp-card h2 { color: #D32F2F; } </style>
Everything under .cmp-card — no leaking styles into the rest of the page.
A <style> tag in the component is fine for learning — it is not how real projects ship CSS.
Why the inline style is temporary — three reasons.
10 cards on a page = 10 copies of the exact same CSS.
No bundling. No minification. Every byte ships raw.
The browser can't cache your styles as one versioned file.
AEM's answer: client libraries — bundled, minified, versioned, loaded once per page. ui.frontend feeds exactly this system. Full video later in the series.
The next deployment would wipe our CRXDE work. Package Manager gets it into the codebase first.
crx/packmgr → Create Package: "card-component".
Edit → Filters → /apps/aeminsider/components/card. The shopping list.
AEM zips that repository path. The zip lands on your machine.
Unzip → copy card → paste into …/jcr_root/apps/aeminsider/components. Commit.
jcr_root/
└── apps/aeminsider/components/card
├── .content.xml ← the node
├── _cq_dialog/ ← cq:dialog
│ └── .content.xml
└── card.html
# cq: on the instance
# becomes _cq_ on disk
Exported + committed = every future deploy ships the card instead of deleting it.
CRXDE Lite is the raw repository. Everything in AEM is stored this way.
/apps = code · /content = data · /conf = config. Mirrors of the codebase.
A cq:Component node + jcr:title + componentGroup + card.html.
name="./title" writes. ${properties.title} reads. Same name, both sides.
Deployed ≠ available. The template's policy allows component groups.
Dialog save → node properties → sling:resourceType → HTL → HTML.
Built in CRXDE to learn fast — then exported to ui.apps. Version-controlled. Deployment-proof.
Subscribe so you don't miss it. Stuck? Component not showing up? Drop a comment — I read all of them.