HTL (HTML Template Language)
Master HTL — AEM's templating language. Learn the syntax, best practices, and how it keeps your templates clean and secure.
Last reviewed: December 2024
🔍 What Is It?
HTL stands for HTML Template Language. It is:
- →AEM's recommended templating language (replaced JSP)
- →HTML-valid — HTL templates are valid HTML files, so designers can open them in a browser
- →Secure by default — HTL auto-escapes output, preventing XSS attacks
- →Logic-free by design — complex logic must go in Sling Models or Use-classes
- →Server-side rendered — runs on the AEM server before sending HTML to the browser
HTL files have the .html extension and live in your component folder alongside the dialog.
🎯 Why Do We Need It?
Before HTL, AEM used JSP (JavaServer Pages). JSP had serious problems:
- →Security holes — developers could write unescaped output, causing XSS
- →Mixed concerns — Java logic mixed with HTML made templates unmaintainable
- →Designer unfriendly — JSP tags broke HTML structure
HTL solved all of this:
- →Auto-escaping prevents XSS by default
- →Separation of concerns — logic stays in Java, templates stay in HTML
- →HTML-valid — designers can open templates in browsers
- →Simpler syntax — data-sly-* attributes are easier to read than JSP tags
🏗️ What Happens in Real Projects?
In a real project, every component has an HTL file. Here's a typical day:
- →You create a component folder with
hero.html - →The HTL file reads data from a Sling Model using
data-sly-use - →It conditionally renders sections using
data-sly-test - →It loops over lists using
data-sly-list - →It includes other components using
data-sly-resource - →It includes sub-templates using
data-sly-templateanddata-sly-call
HTL is the glue between your Java logic and the HTML that authors see in the browser.
⚙️ How It Works
Core HTL Attributes
| Attribute | Purpose |
|---|---|
data-sly-use | Import a Sling Model or Use-class |
data-sly-test | Conditional rendering (like if) |
data-sly-list | Loop over a collection |
data-sly-repeat | Loop and repeat the element itself |
data-sly-text | Set text content |
data-sly-attribute | Set HTML attributes |
data-sly-include | Include another HTL script |
data-sly-resource | Include a child resource/component |
data-sly-template | Define a reusable template |
data-sly-call | Call a defined template |
data-sly-unwrap | Remove the wrapper element |
Expression Syntax
HTL uses ${...} for expressions. You can access properties, call getters, and apply options:
- →
${model.title}— calls model.getTitle() - →
${properties.heading}— reads JCR property directly - →
${item.url @ context='uri'}— output with explicit context - →
${model.count > 0}— boolean expression
Context Options
HTL auto-escapes based on context. You can override:
- →
@ context='html'— output raw HTML (use carefully!) - →
@ context='uri'— for URLs - →
@ context='text'— for plain text (default) - →
@ context='unsafe'— no escaping (avoid!)
💻 Example
HTL Patterns You'll Use Every Day — Real examples covering the most common HTL patterns in enterprise AEM projects.
🤔 Common Confusions
❓ What is the difference between data-sly-text and ${...}?
data-sly-text="value" replaces the entire content of the element. ${value} outputs inline within content. Use data-sly-text when you want to replace all child content; use ${} for inline output within text.
❓ Why does my HTML output show escaped entities like <?
HTL auto-escapes HTML for security. If you need to output raw HTML (e.g., from a Rich Text Editor), use @ context='html': ${model.richText @ context='html'}. Only do this with trusted content — never with user input.
❓ What is <sly> and when should I use it?
<sly> is a virtual element — it's processed by HTL but never rendered in the final HTML. Use it when you need a data-sly-* attribute but don't want to add a wrapper element to your markup.
❓ Can I write if/else in HTL?
Not directly. HTL doesn't have an else statement. The workaround is to use two data-sly-test attributes: one for the true case and one for the false case (negate with !). For complex conditionals, move the logic to your Sling Model.
🚨 Common Production Issues
Blank output
Sling Model failed to instantiate. Check the error.log for model instantiation errors.
XSS vulnerability
developer used @ context='unsafe' or @ context='html' with user-controlled content.
data-sly-list not rendering
the list returned null instead of an empty collection. Always return empty lists, never null.
Template not found
HTL file name doesn't match component node name, or there's a selector mismatch.
Encoding issues
special characters in content appear garbled. Usually a UTF-8 encoding issue in the content or the HTL file itself.
✅ Best Practices
Never use @ context='unsafe'
it disables all XSS protection.
Use <sly> for logic-only elements to keep your HTML clean.
Keep HTL templates simple
if you need more than 3 conditions, move logic to a Sling Model.
Use data-sly-template for repeating patterns within the same file.
Always test your HTL with empty/null data
use DefaultInjectionStrategy.OPTIONAL in models.
Use meaningful variable names in data-sly-use and data-sly-list for readable templates.
🏛️ Architect's Note
HTL's intentional simplicity is a feature, not a limitation. In large teams, it prevents junior developers from putting business logic in templates — which is one of the biggest sources of unmaintainable AEM code.
Governance tip: Establish a rule in your team: if a developer needs more than 2 conditions in HTL, they must move the logic to a Sling Model. This keeps templates clean and testable.
For AEM as a Cloud Service: HTL templates are compiled at build time in ACS, which means syntax errors are caught earlier. Use the HTL Maven Plugin in your build to validate HTL before deployment.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
↗ Where this topic fits
AEM Technology Guide
Return to the AEM learning hub.
AEM Developer Career Path
This AEM developer career path explains how to become an AEM developer through JCR, Sling, HTL, client libraries, Dispatcher, Cloud Service delivery, project depth, and interview-ready enterprise implementation skills.
Drupal Developer Career Path
A practical route through Drupal content modeling, site building, theming, modules, configuration management, APIs, performance, deployment discipline, and interview preparation.
Sources
Sources / References
Use these AEM references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide AEM learning path
Browse the full AEM topic collection and learning order on SplashRide.
AEM Interview Prep
Practice AEM interview questions and scenario-based answers.
AEM Developer Career Path
This AEM developer career path explains how to become an AEM developer through JCR, Sling, HTL, client libraries, Dispatcher, Cloud Service delivery, project depth, and interview-ready enterprise implementation skills.
Drupal Developer Career Path
A practical route through Drupal content modeling, site building, theming, modules, configuration management, APIs, performance, deployment discipline, and interview preparation.
AEM Components Explained: Dialogs, HTL, Sling Models, and Core Components
Learn AEM components with component nodes, dialogs, HTL, Sling Models, Core Components, authoring, inheritance, and real project patterns.
Sling Models
Understand Sling Models — the backbone of AEM component logic. Learn how they work, how to write them, and the gotchas that trip up every developer.