For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/foundation/htl-templates.md.

HTL (formerly Sightly) is AEM's server-side template language. It completely replaces JSP and serves as the standard for rendering components. HTL'

HTL Templates

HTL (HTML Template Language) in AEM

HTL (formerly Sightly) is AEM's server-side template language. It completely replaces JSP and serves as the standard for rendering components. HTL's core philosophy is secure by default — automatically escaping output data to prevent XSS attacks.


1. Expressions

Expressions print values to HTML using $\{\} syntax:

<!-- Print JCR property directly -->
<h1>${properties.jcr:title}</h1>

<!-- Fetch data from Sling Model -->
<p>${model.description}</p>

<!-- Literal string -->
<span>${'Hello, World!'}</span>

1.1. Expression Options

Modify how data is rendered by adding @ following the variable:

<!-- Context (Specify security context) -->
<a href="${link.url @ context='uri'}">${link.text}</a>

<!-- Format (String formatting) -->
<p>${'Price: {0}' @ format=product.price}</p>

<!-- i18n (Internationalization translation) -->
<span>${'Read More' @ i18n}</span>

<!-- Join (Join array elements) -->
<p>${model.tags @ join=', '}</p>

1.2. Display Contexts

Although HTL chooses security context automatically, you can (and sometimes must) override it. Common contexts include:

ContextUsage LocationHTL Example
html (default)Safe HTML content&lt;p&gt;$\{text\}&lt;/p&gt;
textPlain text (Strips HTML)&lt;title&gt;$\{title @ context='text'\}&lt;/title&gt;
attributeHTML tag attribute&lt;div class="$\{cssClass @ context='attribute'\}"&gt;
uriURL path&lt;a href="$\{url @ context='uri'\}"&gt;
scriptStringAssign to JavaScript variablevar x = "$\{val @ context='scriptString'\}"
unsafeBypass security$\{richText @ context='unsafe'\}

Security Warning: NEVER use context='unsafe' unless completely certain content is safe. For Rich Text Editor (RTE) output, RTE sanitizes malicious code on save, and default html context provides extra protection on render. RTE output called via $\{model.richText\} is safe!


2. Block Statements

Block statements are HTML attributes prefixed with data-sly-. They control HTL logic execution.

2.1. data-sly-use (Initialize Model)

Loads a Sling Model or template file into a variable.

<!-- Load Sling Model -->
<div data-sly-use.model="com.mysite.core.models.ArticleModel">
    <h1>${model.title}</h1>
</div>

<!-- Load another HTL file -->
<div data-sly-use.template="partials/header.html"></div>

<!-- Pass parameters into Model -->
<div data-sly-use.model="${'com.mysite.core.models.ListModel' @ limit=5}"></div>

(Variable identifier follows dot — e.g. .model — used in HTL).

2.2. data-sly-test (Conditional Rendering)

Functions like an if statement. Can also be used for variable assignment.

<!-- Render <h1> only if title exists -->
<h1 data-sly-test="${model.title}">${model.title}</h1>

<!-- If/Else pattern with variable assignment (.hasItems) -->
<div data-sly-test.hasItems="${model.items.size > 0}">
    <p>Found ${model.items.size} results</p>
</div>
<div data-sly-test="${!hasItems}">
    <p>No results found.</p>
</div>

Note: Values evaluated as false: null, empty string "", number 0, boolean false, and empty collections/arrays.

2.3. data-sly-list vs data-sly-repeat (Loops)

Both iterate over arrays/collections, but impact DOM structure differently.

Loop Helper Variable (itemList): Inside loops, HTL automatically generates itemList object with properties:

VariableFunction
itemList.indexCurrent index (0-based)
itemList.countCurrent count (1-based)
itemList.first / lastReturns true if first / last element
itemList.middleReturns true if middle element
itemList.odd / evenReturns true if index is odd / even
<ul data-sly-list.item="${model.articles}">
    <li>${item.title} - Viewing item ${itemList.count}</li>
</ul>

2.4. data-sly-resource vs data-sly-include

Embeds another component or HTML file into current template.

  • data-sly-resource: Embeds a component. Triggers Sling Resolution (invokes Java logic, loads child component Dialog).

    <!-- Call component with resourceType -->
    <div data-sly-resource="${'header' @ resourceType='mysite/components/header'}"></div>
    
    <!-- Call with selector -->
    <div data-sly-resource="${'sidebar' @ resourceType='mysite/components/sidebar', selectors='compact'}"></div>
    
    <!-- Change wrapper tag of child component -->
    <div data-sly-resource="${'text' @ resourceType='mysite/components/text', decorationTagName='article'}"></div>
  • data-sly-include: Includes a static HTML snippet directly. Does NOT trigger Sling Resolution.

    <div data-sly-include="partials/footer.html"></div>
    <div data-sly-include="/apps/mysite/components/shared/utils.html"></div>

2.5. data-sly-template / data-sly-call (UI Reuse)

Defines an HTML block as a function and calls it in multiple places.

<!-- Define template -->
<template data-sly-template.card="${@ title, description, link}">
    <div class="card">
        <h3>${title}</h3>
        <p>${description}</p>
        <a href="${link @ context='uri'}">Read More</a>
    </div>
</template>

<!-- Call template with arguments -->
<div data-sly-list="${model.articles}">
    <div data-sly-call="${card @ title=item.title, description=item.excerpt, link=item.path}"></div>
</div>

2.6. data-sly-element (Dynamic HTML Tag)

Dynamically changes HTML element based on configuration.

<!-- If model.headingLevel is 'h2', tag renders as <h2> -->
<h1 data-sly-element="${model.headingLevel || 'h1'}">${model.title}</h1>

2.7. data-sly-attribute (HTML Attributes)

<!-- Set single attribute -->
<div data-sly-attribute.id="${model.anchorId}">Content</div>

<!-- Set multiple attributes via Map -->
<div data-sly-attribute="${model.attributes}">Content</div>

<!-- Conditional attribute (removed automatically if empty/false) -->
<input data-sly-attribute.disabled="${model.isDisabled ? 'disabled' : ''}"/>

2.8. data-sly-text (Override Text)

Retains mockup text for frontend design time, automatically replaced when rendered.

<p data-sly-text="${model.description}">Mock text replaced at runtime.</p>

2.9. data-sly-unwrap (Remove Wrapper Tag)

Keeps inner content while unwrapping host HTML tag from DOM. Useful for logical wrappers without introducing unnecessary wrapper &lt;div/&gt; elements.

<div data-sly-unwrap>
    <p>This paragraph will not be wrapped in div in browser DOM.</p>
</div>

2.10. data-sly-set (Local Variable Assignment)

Introduced in HTL 1.4, assigns calculated values to local variables without creating HTML tags.

<sly data-sly-set.fullName="${model.firstName} ${model.lastName}"/>
<p>Hello, ${fullName}</p>

3. Global Objects

HTL templates automatically inject global objects:

ObjectTypeDescription
propertiesValueMapProperties of current resource (JCR node).
pagePropertiesValueMapProperties of current page (jcr:content node).
inheritedPagePropertiesValueMapInherited properties from ancestor pages.
currentPagePageCurrent AEM Page object.
currentNodeNodeCurrent JCR Node object.
resourceResourceCurrent Sling Resource object.
requestSlingHttpServletRequestCurrent HTTP request object.
responseSlingHttpServletResponseCurrent HTTP response object.
logLoggerOutput log to AEM error.log.
wcmmodeWCMModeCurrent UI mode (edit, preview, disabled).
currentStyleStyleCurrent component Policy configuration.

Practical Example:

<!-- Get page title -->
<title>${currentPage.title || currentPage.name}</title>

<!-- Check Edit mode to render placeholder for authors -->
<div data-sly-test="${wcmmode.edit || wcmmode.preview}">
    <p>Component is empty. Drag and drop data here.</p>
</div>

<!-- Print debug log -->
${'Component rendering started' @ log}

4. The Use API

3 ways to connect HTL with Backend Logic:

Standard approach for 99% of components.

<div data-sly-use.model="com.mysite.core.models.ArticleModel">
    ${model.title}
</div>

Level 2: Call Sling Model with Parameters

Pass parameters from HTL to Java model.

HTL Template:

<div data-sly-use.pagination="${'com.mysite.core.models.PaginationModel' @ currentPage=3, totalPages=10}">
    Page ${pagination.currentPage} / ${pagination.totalPages}
</div>

Java Sling Model:

@Model(adaptables = SlingHttpServletRequest.class)
public class PaginationModel {
    @RequestAttribute
    private int currentPage;

    @RequestAttribute
    private int totalPages;
}

Level 3: Use Javascript Use Objects (Deprecated)

Server-side JS files. Deprecated on AEM as a Cloud Service (AEMaaCS). Always use Sling Models.


5. Practical Example: Article Card Component

<!-- file: article-card.html -->

<!-- 1. Define reusable template -->
<template data-sly-template.articleCard="${@ article}">

    <!-- 2. Verify article object is not null -->
    <article class="cmp-article-card" data-sly-test="${article}">

        <!-- Render image if available -->
        <div data-sly-test="${article.image}" class="cmp-article-card__image">
            <img src="${article.image @ context='uri'}" alt="${article.imageAlt}" loading="lazy"/>
        </div>

        <div class="cmp-article-card__content">
            <!-- 3. Dynamic Element: Render h2, h3, or h4 -->
            <h3 data-sly-element="${article.headingLevel || 'h3'}" class="cmp-article-card__title">
                <a href="${article.link @ context='uri'}">${article.title}</a>
            </h3>

            <p data-sly-test="${article.excerpt}" class="cmp-article-card__excerpt">
                ${article.excerpt}
            </p>

            <span class="cmp-article-card__date">${article.formattedDate}</span>

            <!-- 4. Repeat loop for Tags -->
            <ul data-sly-test="${article.tags}" class="cmp-article-card__tags">
                <li data-sly-repeat="${article.tags}" class="cmp-article-card__tag">
                    ${item}
                </li>
            </ul>
        </div>
    </article>
</template>

<!-- Call Java model and pass model to template -->
<div data-sly-use.model="com.mysite.core.models.ArticleCardModel"
     data-sly-call="${articleCard @ article=model}">
</div>

6. HTL Best Practices

  1. Always Use Sling Models: Keep complex logic in Java; HTL should only handle UI rendering.
  2. Never Use context='unsafe': Unless fully aware of security implications.
  3. Keep Templates Simple: If conditional statements become complex, move logic to Java.
  4. Use Template & Call: Split UI into reusable fragments (data-sly-template/data-sly-call).
  5. Follow BEM Naming: Use cmp- prefix for CSS classes to align with Core Components standards.
  6. Provide Placeholders: Use wcmmode.edit to show placeholder messages when component is unconfigured.
  7. Guard with data-sly-test: Check variables for null before rendering markup.