N
Naveenr.dev
Chapter 06
17 min read2026-06-20

Understanding HTL (Sightly) in AEM

Master HTL, AEM's template language. Learn expressions, conditional rendering, lists, component composition, context-aware escaping, and WCM mode. Understand how HTL bridges Sling Models and HTML output. Troubleshoot common rendering issues.

Content Objective

  • Understand what HTL is and why it replaced JSP
  • Master HTL expressions and common syntax patterns
  • Learn how to use Sling Models in HTL templates
  • Discover conditional rendering with data-sly-test
  • Master list rendering with data-sly-list vs data-sly-repeat
  • Learn component composition with data-sly-resource and data-sly-include
  • Understand context-aware escaping and security
  • Troubleshoot common HTL rendering issues

The Journey So Far

In the previous chapter, we learned how Sling Models prepare data for rendering through adaptation and dependency injection.

A Sling Model can:

  • Retrieve content from the repository
  • Perform validation and business logic
  • Execute complex operations
  • Expose clean data to the view layer

However, one important question still remains:

,[object Object],

A Simple Example

Consider a Title component:

  1. The Sling Model successfully retrieves the title value: "Welcome To AEM"
  2. The value is available inside the model object
  3. But the browser cannot render Java objects
  4. The browser only understands HTML

This is where HTL enters into the picture.

HTL is the presentation layer of modern AEM development.

Its responsibility is simple:

,[object Object],

Where HTL Fits in the Rendering Pipeline

HTL Fit
HTL Fit

HTL bridges the gap between Java objects and HTML markup.

Why HTL Was Created

Before HTL: JSP

Old approach using JSP:

<% String title = "hello"; %>
<h1><%= title %></h1>

Problems:

  • Hard to maintain — Business logic mixed with presentation
  • Hard to secure — Easy to introduce vulnerabilities
  • Hard to read — Java code scattered throughout templates
  • Hard to test — Logic embedded in templates

After HTL: Clean Separation

HTL approach:

<h1>${model.title}</h1>

Benefits:

  • Clean — Simple, readable syntax
  • Secure — Built-in escaping prevents XSS
  • Maintainable — Logic stays in models
  • Testable — Models tested independently

HTL introduced a cleaner separation between data and presentation, making components more maintainable and secure.

Understanding HTL Expressions

Basic Expression Syntax

The most fundamental HTL feature is expression syntax:

${variable}
${object.property}
${list[0]}
${object.method()}

Common Objects Available in HTL

HTL provides several built-in objects automatically:

ObjectPurposeExample
propertiesComponent resource properties$
currentPageCurrent page being rendered$
requestHTTP request object$
resourceCurrent resource$
modelSling Model (when imported)$

Example: Accessing Properties

<!-- Access component properties -->
<h1>${properties.title}</h1>
<p>${properties.description}</p>

<!-- Access page properties -->
<h2>${currentPage.title}</h2>

<!-- Access resource path -->
<span>${resource.path}</span>

Using Sling Models in HTL

The most important pattern in modern AEM development:

Importing a Sling Model

<sly data-sly-use.model="com.company.core.models.TitleModel"/>
<h1>${model.title}</h1>

Breaking it down:

<sly>              → HTL element (renders nothing)
data-sly-use       → Import a Sling Model
.model             → Variable name (arbitrary)
="package.path"    → Full Java class path

How HTL Uses Models

HTL Import
 ↓
Sling Model Class Name
 ↓
Sling Framework
 ↓
Adapts Current Resource to Model
 ↓
@ValueMapValue fields injected
 ↓
@PostConstruct executed
 ↓
Model instance ready
 ↓
HTL can access public getters

Key Principle

HTL does not fetch data directly.

HTL only consumes data exposed by Sling Models.

<!-- WRONG - Direct repository access -->
<h1>${resource.valueMap.title}</h1>

<!-- CORRECT - Through Sling Model -->
<sly data-sly-use.model="com.company.core.models.TitleModel"/>
<h1>${model.title}</h1>

Why? Because:

  • Sling Model contains validation logic
  • Sling Model contains business rules
  • Sling Model is testable
  • Sling Model is reusable
  • HTL remains focused on presentation

The <sly> Element

HTL provides a special element called <sly> that allows developers to execute HTL statements without generating additional HTML output.

Understanding <sly>

Unlike a normal div, span, or section element, the <sly> tag disappears completely after rendering.

Example:

<!-- Before rendering -->
<sly data-sly-test="${model.title}">
    <h1>${model.title}</h1>
</sly>

<!-- After rendering (sly tag is gone!) -->
<h1>Welcome To AEM</h1>

Why <sly> Is Useful

  • Model initialization — Load data without generating HTML
  • Conditional logic — Render content based on conditions
  • Iteration — Loop through items without extra markup
  • Variable assignment — Create temporary variables

Common <sly> Patterns

Model import:

<sly data-sly-use.model="com.company.core.models.TitleModel"/>
<!-- Rest of template -->

Variable assignment:

<sly data-sly-set.pageTitle="${currentPage.title}"/>
<h1>${pageTitle}</h1>

Conditional:

<sly data-sly-test="${model.title}">
    <h1>${model.title}</h1>
</sly>

Conditional Rendering with data-sly-test

data-sly-test renders an element only if a condition is true.

Basic Pattern

<div data-sly-test="${model.title}">
    ${model.title}
</div>

This renders the <div> only if model.title is not null/empty.

If the condition is false, the entire element and its children are removed from output.

Real-World Examples

Show element if title exists:

<h1 data-sly-test="${model.title}">
    ${model.title}
</h1>

<!-- If title is empty, entire h1 is removed -->

Show placeholder if missing:

<div data-sly-test="${model.description}">
    ${model.description}
</div>
<div data-sly-test="${!model.description}">
    <!-- No description available -->
</div>

Author mode specific:

<div data-sly-test="${wcmmode.edit}">
    [Configure Component]
</div>

Only shows in author edit mode.

Common Conditions

ConditionResult
${variable}True if not null/empty
${!variable}True if null/empty (NOT operator)
${variable1 && variable2}True if both are true
${variable1 || variable2}True if either is true

Rendering Lists: data-sly-list vs data-sly-repeat

One of the most common HTL tasks is iterating over collections.

HTL provides two approaches with important differences.

data-sly-list: Creates Loop Variables

data-sly-list creates a loop variable and provides helpful metadata.

Syntax:

<ul>
  <li data-sly-list="${model.items}">${item}</li>
</ul>

What happens:

model.items = ["Apple", "Banana", "Orange"]
                ↓
For each item:
  item = current item
  itemList = metadata (index, first, last, etc.)

Available metadata:

<li data-sly-list="${model.items}">
  ${item}                    <!-- Current item -->
  Index: ${itemList.index}   <!-- 0, 1, 2... -->
  ${itemList.first}          <!-- true/false -->
  ${itemList.last}           <!-- true/false -->
</li>

data-sly-repeat: Repeats Element

data-sly-repeat repeats the existing element for each item but does not provide metadata.

<ul>
  <li data-sly-repeat="${model.items}">${item}</li>
</ul>

Same output as data-sly-list, but no metadata available.

When to Use Each

UseWhen
data-sly-listYou need index, first, last, or other metadata
data-sly-repeatYou just need to repeat an element

Real-World Example

Rendering product list with alternating styles:

<ul>
  <li data-sly-list="${model.products}" 
      class="${itemList.index % 2 == 0 ? 'even' : 'odd'}">
    ${item.name}
  </li>
</ul>

Uses metadata (itemList.index) to alternate CSS classes.

Component Composition

Modern AEM components are often built from smaller components.

HTL provides two ways to include components.

data-sly-resource: Full Component Rendering

Includes another AEM component and triggers the full Sling lifecycle.

<sly data-sly-resource="${'./image' @ 
      resourceType='company/components/image'}"/>

What happens:

    1. ResourceResolver finds child resource
    1. Component Resolution identifies component
    1. Sling Model adapts resource
    1. HTL template renders
    1. Full lifecycle executed

Use when:

  • Including complex components with models
  • Components need full Sling processing
  • Child resource needs adaptation and initialization

data-sly-include: HTL-Only Rendering

Includes another HTL script without full Sling processing.

<sly data-sly-include="image.html"/>

What happens:

    1. Find script file
    1. Include HTL content
    1. Minimal processing

Use when:

  • Simple template inclusion
  • No Sling Model needed
  • Performance-sensitive code

Comparison

Featuredata-sly-resourcedata-sly-include
Sling LifecycleFullNone
Model AdaptationYesNo
Script ResolutionYesNo
Component ReuseFull componentTemplate snippet
PerformanceHeavierLighter

HTL Context-Aware Escaping

One of the most important security features of HTL.

What Is Context-Aware Escaping?

HTL automatically escapes output based on where it appears in the HTML document.

${userInput}

HTL detects the context and applies appropriate escaping:

  • HTML context → HTML escaping
  • JavaScript context → JavaScript escaping
  • URL context → URL escaping
  • CSS context → CSS escaping

Why This Matters

Consider a malicious input:

<script>alert('XSS')</script>

Without escaping (JSP):

<h1><%= userInput %></h1>
<!-- RENDERS: <h1><script>alert('XSS')</script></h1> -->
<!-- DANGEROUS! -->

With HTL (automatic escaping):

<h1>${userInput}</h1>
<!-- RENDERS: <h1>&lt;script&gt;alert('XSS')&lt;/script&gt;</h1> -->
<!-- SAFE! Script cannot execute -->

This Is Why HTL Replaced JSP

HTL's automatic escaping prevents:

  • Cross-Site Scripting (XSS) attacks
  • HTML injection
  • JavaScript injection
  • URL injection

Developers don't need to remember to escape manually.

HTL does it automatically based on context.

WCM Mode and Authoring

HTL templates often behave differently in Author vs Publish environments.

HTL provides wcmmode object to detect the current environment:

WCM Mode Values

<!-- Author Edit Mode -->
<div data-sly-test="${wcmmode.edit}">
    [Component Configuration Required]
</div>

<!-- Preview Mode -->
<div data-sly-test="${wcmmode.preview}">
    Preview content shown
</div>

<!-- Disabled Mode (Publish) -->
<div data-sly-test="${wcmmode.disabled}">
    This appears on Publish
</div>

Real-World Use Cases

Show edit markers in author:

<div data-sly-test="${wcmmode.edit}" class="edit-marker">
    [Edit this component]
</div>

Show placeholder text in author:

<div data-sly-test="${!model.title && wcmmode.edit}">
    [No title configured - Add one in the dialog]
</div>

Different rendering based on environment:

<!-- Author: Show configuration help -->
<div data-sly-test="${wcmmode.edit && !model.configured}">
    Configure this component
</div>

<!-- Publish: Show actual content -->
<div data-sly-test="${wcmmode.disabled || model.configured}">
    ${model.title}
</div>

Real Component Example: Complete End-to-End Flow

Let's trace a Title component from author to browser display.

Step 1: Author Input

Content author enters a title in the component dialog:

Title: "Welcome to AEM"

Step 2: Repository (JCR Storage)

This value is stored in the repository:

/content/site/en/home/jcr:content/root/title
  jcr:title = "Welcome to AEM"
  sling:resourceType = "company/components/title"

Step 3: Sling Model (Business Logic)

The Sling Model processes this data:

@Model(adaptables = Resource.class)
public class TitleModel {
  
  @ValueMapValue
  private String title;
  
  @PostConstruct
  private void init() {
    if (title != null) {
      title = title.trim();
    }
  }
  
  public String getTitle() {
    return title != null ? title : "";
  }
}

Step 4: HTL Template (Presentation)

HTL consumes the Sling Model:

<sly data-sly-use.model="com.company.core.models.TitleModel"/>
<h1 data-sly-test="${model.title}">
    ${model.title}
</h1>
<div data-sly-test="${!model.title && wcmmode.edit}">
    [No title configured]
</div>

Step 5: HTML Generated

The final HTML output:

<h1>Welcome to AEM</h1>

On publish (wcmmode is disabled, title exists).

Step 6: Browser Output

End user sees:

Welcome to AEM
(rendered as h1 heading)

Complete Data Flow

Author enters data
 ↓
Stored in JCR
 ↓
Resource retrieved by ResourceResolver
 ↓
Sling Model adapted from Resource
 ↓
@ValueMapValue injects title field
 ↓
@PostConstruct trims whitespace
 ↓
HTL imports model
 ↓
HTL accesses ${model.title}
 ↓
Context-aware escaping applied
 ↓
HTML generated: <h1>Welcome to AEM</h1>
 ↓
Browser displays heading

Common HTL Mistakes

Mistake #1: Putting Business Logic in HTL

Wrong:

<h1>${properties.title.toUpperCase()}</h1>

Problems:

  • Business logic in presentation
  • Hard to test
  • Hard to maintain

Correct:

// In Sling Model
public String getDisplayTitle() {
  return title != null ? title.toUpperCase() : "";
}
<!-- In HTL -->
<h1>${model.displayTitle}</h1>

Mistake #2: Forgetting to Import Models

Wrong:

<h1>${model.title}</h1>
<!-- model is undefined! -->

Correct:

<sly data-sly-use.model="com.company.core.models.TitleModel"/>
<h1>${model.title}</h1>

Mistake #3: Not Using Context-Aware Escaping

Wrong:

<!-- Trying to output raw HTML -->
<div>${model.htmlContent}</div>
<!-- This gets escaped! -->

Correct (if HTML is safe):

<div data-sly-use.html="${model.getRichTextContent @ unsafe=true}">
    ${html}
</div>

Mistake #4: Complex Conditions in HTL

Wrong:

<div data-sly-test="${model.a && model.b && 
     (model.c || model.d) && !model.e}">
    Too complex in HTL!
</div>

Correct:

// In Sling Model
public boolean shouldDisplay() {
  return model.a && model.b && (model.c || model.d) && !model.e;
}
<!-- In HTL -->
<div data-sly-test="${model.shouldDisplay}">
    Clear and readable
</div>

Troubleshooting: HTL Not Rendering Data

When HTL doesn't display data, follow this checklist:

StepCheckCommon Issue
1Model adapting?Missing @Model annotation
2Getter exists?Method not public
3HTL imports model?Missing data-sly-use
4Expression correct?Typo in property name
5Data in JCR?Property not saved
6Check logs?Errors during adaptation

Debug Process

  • Empty output?

  • Check browser console (no error = HTL compiled ok)

  • Check error.log for adaptation errors

  • Verify property exists in CRX/DE

  • Test model directly (bypass HTL)

  • Add debug output to Sling Model

Why Architects Care About HTL

HTL is often viewed as just a templating language, but it plays a critical role in the AEM architecture.

Resources        → Content
Sling Models     → Business Logic
HTL              → Presentation ← HERE

Clean separation of concerns enables:

  • Maintainability — Changes to logic don't require template updates
  • Testability — Models tested independently of templates
  • Security — Built-in escaping prevents vulnerabilities
  • Performance — Sling Models can be optimized separately
  • Reusability — Models used by multiple templates or APIs

When reviewing component implementations, architects look for:

  • Business logic in Sling Models (not HTL)
  • Presentation logic in HTL (not models)
  • Proper use of <sly> and data attributes
  • Context-aware escaping working correctly
  • No direct JCR access from HTL

Key Takeaways

  • HTL is the presentation layer that consumes Sling Model data
  • HTL expressions (${}) access variables and objects
  • Sling Models are imported using data-sly-use
  • <sly> element executes HTL without generating HTML
  • data-sly-test conditionally renders elements
  • data-sly-list provides loop metadata; data-sly-repeat does not
  • data-sly-resource includes full components; data-sly-include includes HTL only
  • Context-aware escaping prevents XSS automatically
  • WCM mode enables author-specific rendering
  • Business logic belongs in models, not templates

What Happens Next?

In this chapter, we learned how HTL transforms Sling Model data into HTML output.

We've now covered the complete Sling rendering pipeline:

  • Chapter 1: Request Flow & Dispatcher
  • Chapter 2: Resources & ResourceResolver
  • Chapter 3: Component & Script Resolution
  • Chapter 4: URL Decomposition
  • Chapter 5: Sling Models (Adaptation & Injection)
  • Chapter 6: HTL (Presentation Layer)

You now understand the end-to-end flow from browser request to rendered HTML.

From here, you're ready to explore:

  • Advanced HTL — Global objects, formatting, localization
  • Custom Servlets — Building REST APIs with @SlingServlet
  • Query API — Searching content with QueryBuilder
  • Workflows — Content automation and publishing
  • Performance Optimization — Caching, dispatch cache, CDN strategies

The foundation is solid. The advanced topics will build naturally on top of it.