HTL Explained

How AEM Turns Components into HTML

In the previous article, we explored how OSGi Services encapsulate reusable business logic and keep Sling Models focused on preparing presentation data. By the time a request reaches the presentation layer, the content has already been gathered, validated, and transformed into a format that's ready to render.

Now it's time for the final step.

HTL (HTML Template Language) is responsible for turning that prepared data into the HTML returned to the browser. In this article, we'll explore how HTL works, why Adobe created it, and the best practices for building secure, maintainable AEM components.

Reading time: 10–12 min

AEM Cloud Master Series

This article is Part 6 of a 10-part series designed to help frontend engineers and AEM developers understand Adobe Experience Manager from architecture to production deployment.

Series Roadmap

βœ“ Part 1 β€” Understanding Adobe Experience Manager Architecture

βœ“ Part 2 β€” The Complete AEM Request Lifecycle

βœ“ Part 3 β€” Inside Apache Sling

βœ“ Part 4 β€” Sling Models & Dependency Injection

βœ“ Part 5 β€” OSGi Services Explained

βœ“ Part 6 β€” HTL Explained (Current)

Part 7 β€” Dispatcher Deep Dive

Part 8 β€” Building Enterprise Components in AEM

Part 9 β€” React + Adobe AEM Cloud Service

Part 10 β€” From Development to Production

In this article, we'll explore how HTL transforms prepared data into secure, maintainable HTML while keeping presentation separate from business logic.

What Is HTL?

Every web application eventually produces one thing:

HTML.

No matter how sophisticated your backend architecture is, the browser only understands HTML, CSS, and JavaScript.

In AEM, HTL (HTML Template Language) is responsible for generating that HTML.

Unlike JavaServer Pages (JSP), HTL was designed specifically for AEM with a strong emphasis on:

  • readability,

  • security,

  • maintainability,

  • separation of concerns.

Rather than mixing Java code into templates, HTL encourages developers to keep templates focused entirely on presentation.

Where HTL Fits

By the time HTL executes, most of the heavy work has already been completed.

  • Sling has resolved the request.

  • The correct component has been found.

  • The Sling Model has prepared the data.

  • OSGi Services have performed any required business logic.

HTL simply takes that prepared data and renders the final HTML.

Notice that HTL appears at the very end of the pipeline.

It doesn't retrieve repository content.

It doesn't call external APIs.

Its responsibility is presentation.

Why Adobe Created HTL

Before HTL, many AEM projects used JSP.

Although JSP was powerful, it encouraged developers to mix Java code directly into HTML.

A typical template could contain:

  • Java loops

  • Database calls

  • Business logic

  • HTML

As projects grew, those templates became increasingly difficult to read and maintain.

HTL takes a different approach.

Instead of asking templates to perform work, it expects them to display values that have already been prepared elsewhere.

That leads to cleaner code and a much clearer separation between presentation and business logic.

Your First HTL Component

A typical component starts by loading its Sling Model.

<sly data-sly-use.model="com.example.core.models.HeroModel"/>

Once the model is available, displaying values is straightforward.

<section class="hero">

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

    <p>${model.description}</p>

</section>

Notice how little logic appears in the template.

Everything has already been prepared by the Sling Model.

That simplicity is one of HTL's greatest strengths.

The Role of Expressions

The most common HTL feature is the expression syntax.

${model.title}

HTL evaluates the expression and replaces it with the corresponding value.

For example:

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

might produce:

<h1>Introducing Copilot</h1>

This keeps templates easy to read, even for developers who aren't Java experts.

Mental Model

A useful way to think about HTL is to compare it to a restaurant.

  • The JCR stores the ingredients.

  • The Sling Model prepares the meal.

  • The OSGi Service provides specialty ingredients if needed.

  • HTL plates the meal and serves it to the customer.

HTL doesn't cook.

It presents.

That mindset alone leads to much cleaner component design.

The Power of data-sly-* Attributes

One of the things that makes HTL different from many template languages is that it doesn't introduce lots of new HTML tags.

Instead, it extends normal HTML using data-sly-* attributes.

That means your templates remain valid HTML while gaining dynamic behavior.

For example:

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

is simply HTML with an HTL expression.

Need to show content only under certain conditions?

Use data-sly-test.

Need to render a list?

Use data-sly-list.

Need to include another template?

Use data-sly-resource.

Rather than inventing a completely new syntax, HTL builds on HTML developers already know.

Conditional Rendering

Many components display optional content.

Imagine a Hero component where the subtitle isn't required.

Instead of writing complicated logic, HTL lets you conditionally render the element.

<p data-sly-test="${model.subtitle}">
    ${model.subtitle}
</p>

If subtitle contains a value, the paragraph is rendered.

If it's empty or null, HTL removes the entire element.

The browser never sees an empty <p> tag.

This keeps the generated HTML clean and semantic.

Rendering Lists

Lists are another common requirement.

Suppose your Sling Model returns a list of products.

public List<Product> getProducts() {
    return products;
}

Rendering them is straightforward.

<ul data-sly-list.product="${model.products}">
    <li>${product.title}</li>
</ul>

If the model contains three products, HTL generates:

<ul>
    <li>Surface Laptop</li>
    <li>Surface Pro</li>
    <li>Surface Studio</li>
</ul>

Notice how the template focuses entirely on the HTML structure.

It doesn't care where the products came from.

Including Other Components

Large pages are rarely built from a single template.

Instead, they're composed of many smaller components.

HTL makes this easy with data-sly-resource.

<div
    data-sly-resource="${'hero' @ resourceType='my-site/components/hero'}">
</div>

Rather than duplicating markup, one component can render another.

This encourages composition instead of duplication.

A homepage, for example, might include:

  • Hero

  • Featured Products

  • Testimonials

  • Footer

Each section remains an independent component that can be reused elsewhere.

Component composition is one of the reasons AEM scales so well for large websites.

Loading Client Libraries

A component usually needs more than HTML.

It often requires CSS and JavaScript as well.

Instead of linking files manually, HTL integrates with Client Libraries.

A common pattern looks like this.

<sly
data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"/>

<sly
data-sly-call="${clientlib.css @ categories='my-site.hero'}"/>

<sly
data-sly-call="${clientlib.js @ categories='my-site.hero'}"/>

This tells AEM to include the CSS and JavaScript associated with the my-site.hero Client Library category.

The component remains self-contained, and asset loading stays consistent across the application.

Production Tip

Avoid placing large amounts of JavaScript directly inside HTL templates.

Instead, keep templates focused on structure and load behavior through Client Libraries. This keeps components easier to maintain, improves caching, and allows frontend assets to evolve independently from the server-side rendering layer.

Writing Maintainable HTL

One of the easiest ways to evaluate an HTL template is to ask a simple question:

Could a frontend developer understand this without reading Java code?

A clean template should mostly contain:

  • semantic HTML,

  • a few HTL expressions,

  • simple conditions,

  • loops,

  • component composition.

If it starts looking like a programming language, it's usually a sign that too much logic has leaked into the presentation layer.

HTL in the Rendering Pipeline

Let's place HTL back into the complete request lifecycle.

Notice that HTL is one of the last steps.

By the time execution reaches the template:

  • the resource has already been resolved,

  • the Sling Model has already prepared the data,

  • any business logic has already been executed.

HTL transforms presentation-ready data into semantic, secure HTML that can be delivered directly to the browser

That's exactly what a template engine should do.

Security by Default

One of the reasons HTL became the standard templating language for AEM is its focus on security.

Templates often display content entered by authors.

That content could include:

  • page titles,

  • descriptions,

  • links,

  • image captions,

  • user-generated content.

If that data is rendered without proper escaping, the application becomes vulnerable to attacks such as Cross-Site Scripting (XSS).

HTL automatically escapes output based on the context in which it's rendered.

For example:

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

If the title contains HTML or JavaScript, HTL escapes it before sending it to the browser.

Developers don't need to manually escape every value, reducing both repetitive code and the risk of security mistakes.

This "secure by default" philosophy is one of HTL's biggest advantages over older template technologies.

Placeholder Templates

During authoring, components aren't always fully configured.

An author may drag a Hero component onto a page but forget to enter a title or select an image.

Instead of rendering broken HTML, many AEM projects display a placeholder while authors edit the page.

A simplified example might look like this:

<sly data-sly-test="${wcmmode.edit && !model.title}">
    <div class="cq-placeholder">
        Configure Hero Component
    </div>
</sly>

Visitors never see this placeholder.

It's only shown in Author mode, making unfinished components easier for content authors to identify.

HTL Best Practices

Over time, a few patterns consistently lead to cleaner and more maintainable templates.

Keep HTL focused on presentation

HTL should describe what the page looks like, not how the data is produced.

Good:

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

Avoid templates that perform complex formatting or business logic.

Keep markup semantic

HTL extends HTMLβ€”it shouldn't replace it.

Use proper HTML elements.

<header>

<main>

<section>

<article>

<footer>

Semantic markup improves accessibility, SEO, and maintainability.

Prefer reusable components

If the same markup appears multiple times, consider creating a reusable component instead of copying HTML between templates.

Small reusable components are much easier to maintain than large monolithic templates.

Keep templates readable

A good HTL template should be understandable even by someone who has never seen the component before.

If a frontend developer can open the template and immediately understand its structure, you've probably designed it well.

Common Mistakes

After reviewing many AEM projects, a few patterns appear repeatedly.

Putting Business Logic in HTL

This is probably the most common mistake.

Templates start containing:

  • formatting,

  • calculations,

  • API calls,

  • validation,

  • repository access.

Those responsibilities belong elsewhere.

Remember:

  • Sling Models prepare data.

  • OSGi Services perform business logic.

  • HTL renders HTML.

Repeating Markup

Sometimes developers duplicate the same HTML across several components.

That works initially, but every future change has to be repeated in multiple places.

Instead, build reusable components and compose larger pages from smaller pieces.

Ignoring Semantic HTML

Because HTL looks like ordinary HTML, it's easy to forget that the HTML still needs to follow best practices.

Choosing meaningful elements improves:

  • accessibility,

  • SEO,

  • maintainability.

HTL doesn't replace good frontend development practicesβ€”it builds on them.

HTL Rendering Pipeline

Let's look at the complete rendering process one last time.

Everything before HTL prepares the data.

HTL simply transforms that prepared data into HTML.

Final Thoughts

One of the reasons HTL has remained the standard templating language for AEM is its simplicity.

It encourages developers to separate responsibilities instead of mixing business logic with presentation.

When every layer does one job well:

  • Sling resolves the request.

  • Sling Models prepare the data.

  • OSGi Services handle reusable business logic.

  • HTL renders HTML.

The result is an application that's easier to understand, easier to test, and easier to maintain.

As projects grow, that separation becomes increasingly valuable because each layer can evolve independently without affecting the others.

HTL Best Practices Checklist

Before considering a component complete, ask yourself:

  • Is the HTL template mostly HTML?

  • Is all business logic outside the template?

  • Is presentation data prepared by the Sling Model?

  • Are reusable operations delegated to OSGi Services?

  • Is the markup semantic and accessible?

  • Is the template easy for another developer to read?

  • Can this component be reused elsewhere?

If the answer is "yes" to those questions, you're probably following the design principles HTL was created to encourage.

Key Takeaways

  • HTL is AEM's server-side templating language.

  • Its responsibility is to generate HTML, not execute business logic.

  • Use data-sly-* attributes to add dynamic behavior while keeping templates valid HTML.

  • Let Sling Models prepare presentation-ready data before rendering begins.

  • Delegate reusable business logic to OSGi Services.

  • Build small, readable, reusable templates composed of semantic HTML.

  • Rely on HTL's built-in output escaping to help protect against common security vulnerabilities.

The strongest HTL templates are often the simplest ones. When you can open a component and immediately understand its structure without tracing complex logic, you've achieved the separation of concerns that AEM was designed to promote.

Continue Reading

You've now reached the final step of AEM's rendering pipeline. By combining Sling Models, OSGi Services, and HTL, you've seen how AEM transforms authored content into secure, maintainable HTML.

In the next article, we'll step outside the rendering pipeline and examine one of the most important production components in Adobe Experience Manager: Dispatcher. You'll learn how caching, security filtering, and cache invalidation help enterprise websites remain fast, secure, and scalable under heavy traffic.

Next Article β†’ Part 7: Dispatcher Deep Dive

Masoud

September 8th, 2025