Skip to content

HTL in AEM: The HTML Template Language (Sightly) Tutorial

HTL (Sightly) in AEM: expression syntax, display contexts, all 12 data-sly block statements and their priority, if/else and list examples, and the full HTL reference.

Rewritten in October 2026 and checked against the HTL 1.4 specification and Adobe's HTL documentation.

HTL, the HTML Template Language, is the server-side template system Adobe recommends for HTML in Adobe Experience Manager. It replaced JSP as the default way to write component markup. HTL arrived with AEM 6.0 under the name Sightly and was renamed in August 2016. The rename changed nothing in the code, so "Sightly" and "HTL" mean the same thing.

This page is the overview and quick reference. Each block statement and feature also has its own page with examples, listed in the HTL reference at the end.

How HTL works

An HTL file is an HTML5 file with two additions: expressions in ${...} and block statements written as data-sly-* attributes. AEM compiles each HTL script into a Java servlet. Expressions and block statements are evaluated on the server, and none of them reach the browser: the output is plain HTML.

HTL comes in three layers. The specification is open source and lives at github.com/adobe/htl-spec. The Apache Sling HTL Scripting Engine is its reference implementation, and AEM runs it. AEM adds a few extensions on top, such as extra global objects and options.

Why HTL instead of JSP

  • XSS protection by default. HTL escapes every expression according to where it sits in the markup. A value in an href is checked as a URI, a value in text is HTML-encoded. In JSP the developer has to pick the right escaping for every variable by hand, and one missed call is an XSS hole.
  • Logic stays out of the markup. HTL has no arithmetic and no string manipulation on purpose. Anything beyond showing a value or a condition goes into a Use-API object, normally a Sling Model.
  • The template stays valid HTML. Statements are attributes on ordinary elements, so a front-end developer can read and edit the file without knowing Java.

You do not have to migrate everything at once. HTL and JSP work side by side, even inside one component: a JSP can include an HTL script with <cq:include script="template.html"/>, and HTL can include a JSP with data-sly-include. Adobe says it has no plans to discontinue JSP.

HTL in 2026: what is current

  • The current specification is HTL 1.4, released in June 2018. No newer version exists. The Sling engine implements 1.4 in full.
  • HTL 1.4 added data-sly-set, the in operator, begin/step/end on lists, and an implicit uri context for href, src and similar attributes.
  • The JavaScript Use-API is deprecated on AEM as a Cloud Service. Write logic in Java, with Sling Models.
  • For brand-new projects, Adobe's HTL documentation now points to Edge Delivery Services. HTL remains the template language for components rendered by AEM itself, which is what existing AEM Sites projects and the Core Components use.

Expression syntax

An expression is ${ expression @ options }. The parts you will use every day:

  • Property access: ${properties.jcr:title}, ${currentPage.title}, ${obj['key']}, ${list[0]}.
  • Literals: booleans, numbers, strings in single or double quotes, and arrays such as [1, 2, 'three']. See HTL literals.
  • Operators: !, &&, ||, the ternary ? : (with spaces around the colon), strict comparisons == != < <= > >= and, since 1.4, in. See HTL operators.
  • Fallbacks: && and || return one of their operands, as in JavaScript, so ${properties.pageTitle || properties.jcr:title || resource.name} prints the first value that is set.
  • False values: false, 0, the empty string and the empty array. The string "false" counts as true.

Expressions can appear in text and in attribute values, never in element or attribute names. To build those, use data-sly-element and data-sly-attribute.

Expression options

  • context: overrides the display context, see the next section.
  • format: fills placeholders, as in ${'Asset {0} of {1}' @ format=[current, total]}, and formats dates and numbers.
  • i18n: translates a string, with optional locale and hint.
  • join: joins an array, as in class="${classes @ join=' '}".
  • URI options such as extension, selectors, addQuery and appendPath rewrite a link.

Details and examples: HTL options and HTL URL, date and number formatting.

Display contexts

The display context decides how a value is escaped. HTL picks one from the position of the expression, and you can override it with @ context.

ContextUsed for
textThe default in text nodes. Encodes HTML special characters.
attributeThe default in attribute values.
uriThe default for href, src, action and other URL attributes. An invalid URI prints nothing.
htmlMarkup you want rendered, such as a rich-text property. Dangerous tags are filtered out.
numberNumbers only.
scriptString, scriptTokenValues inside <script>: strings and JavaScript tokens.
styleString, styleTokenValues inside <style> or CSS.
unsafeTurns escaping and XSS protection off completely. Avoid it.

Inside <script> and <style>, and in on* and style attributes, a context is mandatory. Without one the expression prints nothing, which is the usual reason a value "disappears" from inline script:

<script> var trackingId = "${trackingId @ context='scriptString'}"; </script>

The 12 block statements

HTL 1.4 has twelve block statements. When several sit on one element, they run in a fixed priority order, shown in the last column (1 runs first).

StatementWhat it doesPriority
data-sly-templateDeclares a reusable block of markup with parameters. The element itself is never output.1
data-sly-setDefines a variable: data-sly-set.title="${properties.jcr:title}".2
data-sly-testKeeps or removes the element on a condition, and can store the result for if/else.2
data-sly-useLoads logic: a Sling Model, a Java class or another HTL file.2
data-sly-callCalls a template with parameters.3
data-sly-textReplaces the element's content with a value.4
data-sly-elementChanges the element's tag name.5
data-sly-includeIncludes the output of another script (HTL or JSP) in the same request.5
data-sly-resourceRenders another resource, such as a child component, in a new request context.5
data-sly-unwrapRemoves the element but keeps its content.6
data-sly-listRepeats the element's content for each item.7
data-sly-repeatRepeats the whole element for each item.7
data-sly-attributeSets one attribute or a map of attributes.8

The order has a practical consequence. A data-sly-test on the same element as data-sly-list runs before the loop, so it cannot see item. Put the test on a child element if it depends on the current item.

When no existing element can carry a statement, use <sly>. The sly element is removed from the output and only its content is rendered. For comments that should not reach the browser, use <!--/* ... */-->; ordinary HTML comments are output, and expressions inside them are evaluated.

Examples

Four patterns you will write again and again.

If/else with data-sly-test

HTL has no else statement. Store the result of the test in a variable and test its negation:

<h1 data-sly-test.hasTitle="${properties.jcr:title}">${properties.jcr:title}</h1>
<h1 data-sly-test="${!hasTitle}">${resource.name}</h1>

More patterns on the data-sly-test page.

A list with itemList

Inside a list, the helper object, here childList because the item is named child, has index (from 0), count (from 1), first, middle, last, odd and even:

<ul data-sly-list.child="${currentPage.listChildren}">
    <li class="${childList.first ? 'first' : ''}">
        ${childList.count}. <a href="${child.path}.html">${child.title}</a>
    </li>
</ul>

If the list is empty, the ul is not output at all. To repeat the li itself rather than its content, use data-sly-repeat.

A template in its own file

<!--/* templateLib.html */-->
<template data-sly-template.heading="${@ title, level}">
    <h2 data-sly-element="${level || 'h2'}">${title}</h2>
</template>

<!--/* component.html */-->
<sly data-sly-use.lib="templateLib.html"
     data-sly-call="${lib.heading @ title=properties.jcr:title, level='h3'}"/>

A called template does not see the caller's variables. Pass everything it needs as parameters; a parameter you leave out becomes an empty string.

A Sling Model and a client library

<div data-sly-use.model="com.example.core.models.Teaser">
    <p>${model.description}</p>
</div>
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"
     data-sly-call="${clientlib.css @ categories='mysite.base'}"/>

Adobe recommends Sling Models for component logic in production. The older WCMUsePojo class still works; the difference is explained in Sling Models vs WCMUsePojo.

Global objects

These are available in every HTL script without a data-sly-use:

  • properties, pageProperties, inheritedPageProperties: the properties of the current resource, the current page, and inherited page properties.
  • currentPage, resource, request, wcmmode, currentStyle, currentContentPolicy, pageManager, log and others.

The full list with Java types is on HTL variables.

Tools

  • HTL Maven Plugin (org.apache.sling:htl-maven-plugin): its htl:validate goal checks HTL at build time for syntax errors, missing display contexts and misused block statements.
  • HTL REPL (github.com/adobe/aem-htl-repl): a live HTL editor that runs on a local AEM instance, useful for learning and quick experiments.
  • AEM Core Components: their HTL is Adobe's own reference for how production components are written. See AEM Core Components.

Common questions

Is HTL the same as Sightly?

Yes. Sightly was the name in AEM 6.0 and 6.1. It became HTL in August 2016 without any change to code or APIs.

What is the difference between data-sly-list and data-sly-repeat?

data-sly-list writes the element once and repeats its content. data-sly-repeat repeats the element itself.

How do I output HTML from a rich-text field?

Use ${properties.text @ context='html'}. It renders the markup and filters dangerous tags. context='unsafe' also works, but it turns XSS protection off.

Can I still use the JavaScript Use-API?

It is deprecated on AEM as a Cloud Service. Use Java, preferably a Sling Model.

HTL reference

Every page in the series, with examples.

Basics

Block statements

More

More in Tutorial series

  • AEM as a Cloud Service Tutorial

    What AEM as a Cloud Service is, how it differs from AEM 6.5, author, publish and preview, environments and RDEs, the SDK, pipelines and logs, and a learning path.

  • AEM Tutorial Series: Learn AEM Step by Step

    An ordered AEM learning path based on AEM 6.4: what AEM is, installation, architecture, environments, consoles, authoring, packages, components, templates, assets and workflows.

  • AEM 6.4 and 6.5 Tutorials

    AEM 6.4 and 6.5 tutorials in one place: features, consoles, templates, core components, component patterns, content fragments, content services, personalization and upgrades.

  • Useful AEM Consoles in AEM 6.4 (with URLs)

    Every useful AEM console and its URL: admin, site admin, DAM, CRXDE Lite, Package Manager, OSGi web console, query debugger, dumplibs, JMX and more.