HTL (HTML Template Language, formerly known as Sightly) is a scripting language invented by Adobe specifically for Sling:
- Recommended default scripting language
- Enforces clean separation of presentation and logic — only simple logic constructs in template
- Intelligent XSS support built-in
- Good integration with Sling
File extension: .html. Each HTML file is a valid HTL file. Data logic is added using data-sly-* attributes and ${} expressions.
HTL expressions ${} access variables and their properties:
${currentPage.title}
${properties.jcr:title}
${properties['jcr:title']}Supported: string, number, boolean, array literals. Operators: logical (&&, ||, !), comparison (<, >, ==), ternary (? :), grouping.
Expression options modify the output. Syntax: ${expression @ option1='value', option2='value'}
@ i18n— translate string@ format— format with placeholders@ join— join array with separator@ context— set display/XSS context@ scheme, domain, path, extension, selectors, query, fragment— URI manipulation
HTL applies automatic context-aware XSS escaping. It detects HTML, attributes, URIs, JavaScript, CSS contexts:
${properties.jcr:title} <!-- auto-detected: text context -->
${properties.richText @ context='html'} <!-- explicit: allow safe HTML -->
${properties.link @ context='uri'} <!-- explicit: URI context -->Available contexts: text, html, attribute, uri, number, scriptToken, scriptString, scriptComment, styleToken, styleString, styleComment, unsafe.
Warning: unsafe disables all XSS protection. Use only in exceptional cases.
<!--/* data-sly-test: conditional rendering */-->
<p data-sly-test="${properties.showText}">Text to show</p>
<p data-sly-test="${!properties.showText}">Fallback text</p>
<!--/* Saving test result to variable */-->
<p data-sly-test.hasTitle="${properties.jcr:title}">has title</p>
<p data-sly-test="${!hasTitle}">no title</p>
<!--/* data-sly-list: iterate over items */-->
<ul data-sly-list.child="${resource.listChildren}">
<li>${child.name} (index: ${childList.index})</li>
</ul>
<!--/* data-sly-repeat: like list but keeps host element */-->
<div data-sly-repeat.child="${resource.listChildren}">
${child.name}
</div>
<!--/* Define template with parameters */-->
<template data-sly-template.card="${@ title, description}">
<div class="card">
<h3>${title}</h3>
<p>${description}</p>
</div>
</template>
<!--/* Call template */-->
<sly data-sly-call="${card @ title='Neo', description='The One'}"/>
<!--/* Include template library from separate file */-->
<sly data-sly-use.lib="templateLib.html"
data-sly-call="${lib.card @ title='Morpheus', description='Captain'}"/>
<!--/* Recursive example (site map) */-->
<template data-sly-template.listChildren="${@ page}"
data-sly-list="${page.listChildren}">
<li>
<div class="title">${item.title}</div>
<ol data-sly-call="${listChildren @ page=item}"></ol>
</li>
</template>
<!--/* data-sly-unwrap: conditionally remove outer element */-->
<div class="editable" data-sly-unwrap="${!wcmmode.edit}">
text
</div>
<!--/* data-sly-element: change element name */-->
<h1 data-sly-element="${titleElement}">Title</h1>
<!-- Output: <h3>Title</h3> (if titleElement = 'h3') -->
<!--/* data-sly-attribute: set attributes */-->
<input data-sly-attribute="${keyValueMapOfAttributes}"/>
<!-- Output: <input type="text" name="firstName" value="Alison"/> -->
<!--/* Single attribute with identifier */-->
<a href="#" data-sly-attribute.href="${link}">link</a>
<!--/* data-sly-text: replace element content */-->
<div data-sly-text="${content}">Lorem ipsum</div>
<!--/* Java (Sling Model) example: */-->
<div data-sly-use.logic="org.matrix.SentinelModel">
${logic.status}
</div>
<!--/* JavaScript Use API: */-->
<div data-sly-use.logic="${'logic.js' @ param1='Matrix'}">
${logic.hi}
</div>
<!--/* logic.js */-->
<!--/*
use(function () {
return {
hi: "Hello " + this.param1
};
});
*/-->
<!--/* Resource object (exposes properties) — since HTL 1.2 */-->
<div data-sly-use.nav="./navigation">
${nav.title}
</div>
- Don't use data-sly-use when not needed — if you only read properties and need simple logic, use HTL directly
- Don't include the same Java/JS object multiple times — declare once at top and reuse
- Don't always use 1:1 relation between script and controller — define controllers per aspect of functionality
- A HTL script can use multiple data-sly-use statements for different aspects
- Recommended: Sling Models as controllers (not WCMUse or raw Java classes)