Visual Content Fragments - Templates visual-content-fragments-templates
In Adobe Experience Manager (AEM) as a Cloud Service, HTML templates can be used to visualize Content Fragments and deliver them in HTML format.
HTML templates allow you to control how your Content Fragments are displayed. You can create HTML templates in your code editor of choice, then upload and assign them to Content Fragment Models in AEM. Content placeholders using Handlebars.js allow mapping the template to data types in the Content Fragment Model. Once assigned to a model, a template is available to be used with any Content Fragment based on the model, to visualize the fragment or to delivery it as a modular experience in HTML format to any channel, for example web, email, mobile application, or others.
This article explains how to create custom HTML templates with Handlebars syntax for rendering Visual Content Fragments.
After creating your templates you can then:
What you will learn what-you-will-learn
After providing a (very quick) introduction to:
- How to use your templates in AEM
- Using the Publish URL
This page covers (in more detail):
- Handlebars - the necessary basics of the syntax
- How to access Content Fragment data
- Working with nested Content Fragments
- Handling multi-valued fields
- Creating loops and conditional logic
- Best practices of template design for Content Fragments
Prerequisites prerequisites
To understand and work with the technologies covered here you should have:
- Basic understanding of HTML
- Familiarity with AEM Content Fragments and Content Fragment Models
- Understanding of your Content Fragment models
Using a Content Fragment HTML template using-a-content-fragment-html-template
Using a Content Fragment HTML template in AEM using-a-content-fragment-html-template-in-aem
For details of how to use your template in AEM see:
Using the Visual Content Fragment Publish URL using-the-visual-content-fragment-publish-url
Once you have created Visual Content Fragments using the template you can then use the Publish URL of your Visual Content Fragments.
Handlebars - the (very) basics handlebars-the-very-basics
Handlebars is a simple templating language that uses double curly braces (brackets) {{ }} to insert dynamic content into HTML.
Basic syntax basic-syntax
An example of basic Handlebars syntax:
Key concepts key-concepts
The key concepts of Handlebars:
{{ }}{{{ }}}{{! }}{{{ }}}) for field values because values are pre-rendered HTML.Template context reference template-context-reference
When your template is rendered, it receives a context object containing all the data about your Content Fragment. This will cover:
-
the fragment that you have selected
-
all further fragments referenced from that selected fragment
note NOTE Fragments can be referenced: - in the UI: to the maximum depth of 5
- when using the API: the depth is configurable, up to the maximum depth of 10
Content Fragment content-fragment
The structure of the context object for the (selected) Content Fragment:
fieldsallFields{name, value} for iterationhasFieldstrue if the fragment has fieldsProperties structure properties-structure
The properties object has the same structure for the selected fragment and for each referenced fragment.
idtitledescriptionpath/content/dam/...hasDescriptiontruecreatedDatemodifiedDatepublishedDatestatusDRAFTmodelid, path, name, technicalName, descriptionvalidationStatus{property, message}previewReplicationStatustagsid, title, titlePath, name, path, descriptionfieldTagstags.Examples: Template access
For the (selected) Content Fragment:
Referenced Content Fragments referenced-content-fragments
The structure of the context object for any referenced fragments:
hasReferencedFragmentstrue when references existreferencedFragmentsreferencesErrortrue if an error occurred when loading referencesreferencesErrorMessagereferencesError is trueReferenced Fragment Structure referenced-fragment-structure
Each item in referencedFragments contains:
anchorIdpropertieshasFieldsfieldsallFields{name, value} for iterationExamples: Template access for the first referenced Content Fragment (first item in the 0-indexed list):
Or from the fields map:
Basic field access basic-field-access
Direct field access is recommended, when necessary you can iterate through all fields.
Direct field access (recommended) direct-field-access-recommended
Access fields directly by name using the fields map:
Remember:
- Use triple braces
{{{ }}}for field values if they contain pre-rendered HTML (rich text) - Field names (title, subtitle, description, primaryImage) must match your Content Fragment Model exactly
- Missing fields are not rendered - no errors are thrown and the Handlebars syntax remains present (and visible) in the rendered HTML Fragment
Iterate through all fields iterate-through-all-fields
Use allFields when you do not know the field names in advance:
Remember:
{{name}}uses double braces (plain text label){{{value}}}uses triple braces (pre-rendered HTML value)
Nested Content Fragments nested-content-fragments
When a Content Fragment field references another Content Fragment, you can use dot notation to directly access fields in the referenced fragment.
Single-level nesting single-level-nesting
An example for single-level nesting:
Pattern: fields.referenceFieldName.nestedFieldName
Multi-level nesting multi-level-nesting
The system supports unlimited nesting depth:
Pattern: fields.level1.level2.level3.fieldName (limited depth; default is 5, can be extended to 10 when using the API)
API parameter requirement: hydration api-parameter-requirements
To enable nested Content Fragment access, you must include the hydration query parameter in your API call:
To enable Hydration:
# Enable hydration with depth=2 for 2 levels of nesting
GET /adobe/sites/cf/fragments/{id}/preview?hydration=%7B%22enabled%22%3Atrue%2C%22maxDepth%22%3A2%7D
maxDepth123+Multi-valued fields multi-valued-fields
There are several types of multi-valued fields.
Multi-valued text fields multi-valued-text-fields
Text, number, date, and other simple fields become arrays when multi-valued:
Remember, when accessing array items by index in Handlebars:
- Use:
.[0](dot before bracket)
- Not:
[0]
Multi-valued number fields multi-valued-number-fields
Numbers are converted to strings for rendering:
Multi-valued Content Fragment references multi-valued-content-fragment-references
When a field references multiple Content Fragments:
Multi-valued Asset references multi-valued-asset-references
Content Reference fields configured for content types that are assets (for example, images and documents) are pre-rendered as HTML. Multi-valued assets become arrays:
Nested multi-valued references nested-multi-valued-references
Multi-valued references can contain multi-valued references at any depth:
Loops and iteration loops-and-iteration
Handlebars provides the {{#each}} helper for iterating over arrays and objects.
Iterating over arrays iterating-over-arrays
An example of iterating over arrays:
Special variables in loops special-variables-in-loops
Inside {{#each}} blocks, Handlebars provides special variables:
Iterating over referenced fragments iterating-over-referenced-fragments
An example of iterating over referenced fragments:
Nested loops nested-loops
An example of nested loops:
Conditional rendering conditional-rendering
Use conditionals to show or hide content based on data availability.
Basic If/Else basic-if-else
An example of a basic if-else construct:
Unless (negative conditional) unless-negative-conditional
An unless helper:
Nested Conditionals nested-conditials
An example of nested conditional:
Built-in Handlebars helpers built-in-handlebars-helpers
Handlebars includes several built-in helpers, beyond {{#if}} and {{#each}}.
{{#if condition}}false, undefined, null, 0, "", []{{#unless condition}}#if){{#each array}}{{else}} for empty arrays{{#with object}}{{lookup this "key"}}With Helper with-helper
Creates a new scope for nested objects to reduce repetitive path prefixes:
Advance Patterns advanced-patterns
Some examples of advanced patterns follow.
Accessing Parent Context in Nested Loops accessing-parent-context-in-nested-loops
Use ../ to access the parent scope from within a nested loop:
Dynamic CSS Classes dynamic-css-classes
An example of dynamic CSS classes:
Complete Examples complete-examples
Several complete examples are provided for reference.
Blog post with author
A blog post with author details:
Required API call:
GET /adobe/sites/cf/fragments/{id}/preview?hydration=%7B%22enabled%22%3Atrue%2C%22maxDepth%22%3A1%7D
Generic table view (no prior knowledge of fields) generic-table-view-no-prior-knowledge-of-fields
A generic table view, without an inherent knowledge of fields. The is similar to the Generic Template:
Best practices best-practices
Best practices include:
-
Always use triple braces for field values that contain HTML markup content.
-
Field values are pre-rendered HTML.
[!NOTE]
Double braces show raw HTML tags as plain text.
code language-handlebars <!-- CORRECT --> {{{fields.description}}} <!-- WRONG - displays HTML tags as text --> {{fields.description}} -
-
Check for existence before accessing nested fields.
code language-handlebars <!-- GOOD: check before accessing nested fields --> {{#if fields.author}} <p>By {{{fields.author.name}}}</p> {{/if}} <!-- RISKY: may render empty if author is not set --> <p>By {{{fields.author.name}}}</p> -
Use direct field access when possible.
- It is more readable, and maintainable, than iterating
allFieldsand matching by name.
- It is more readable, and maintainable, than iterating
-
Structure templates with section comments.
code language-handlebars {{! ===== HEADER SECTION ===== }} <header> <h1>{{properties.title}}</h1> </header> {{! ===== MAIN CONTENT ===== }} <main> {{#if hasFields}} <!-- fields rendering --> {{/if}} </main> {{! ===== REFERENCES ===== }} {{#if hasReferencedFragments}} <!-- references rendering --> {{/if}} -
Handle missing data gracefully with fallbacks.
code language-handlebars {{#if fields.title}} <h1>{{{fields.title}}}</h1> {{snippet-not-found:else}} <h1>Untitled</h1> {{/if}} -
Always use a proper HTML document structure.
code language-handlebars <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{properties.title}}</title> </head> <body> <!-- your content here --> </body> </html> -
Test with a variety of content scenarios:
- All fields fully populated
- Optional fields missing
- Empty multi-valued fields
- Deep nesting (multiple levels)
- References that fail to load
-
Use semantic HTML elements:
- For better accessibility use
<article>,<header>,<main>,<footer>,<time>,<address>, or similar.
- For better accessibility use
-
Keep styles in CSS.
- Use
<style>tags or external stylesheets. - Avoid inline styles where possible.
- Use
-
Document complex logic:
- Use Handlebars comments
({{! }}). - Do not use HTML comments, which appear in rendered output.
- Use Handlebars comments
Troubleshooting troubleshooting
Some troubleshooting hints include:
<p>Hello World</p> displayed literally{{{fields.description}}}{{{fields.author.name}}} is blankmaxDepth is deep enough{{#each fields.tags}} to iterate all items{{{fields.tags[0]}}} renders empty{{{fields.tags.[0]}}}hasReferencedFragments is always false?hydration=%7B%22enabled%22%3Atrue%7D; also check {{#if referencesError}}{{#if}} or {{#each}} blocks; add diagnostic output: <pre>hasFields: {{hasFields}}{{! comment }} instead of HTML <!-- comment -->{{#if fields.enabled}} is always truthy"false" is truthy in Handlebars. Only actual false, null, undefined, 0, "", and [] are falsy.<, & shown instead of <, &{{{fields.content}}}#each is undefined../ for parent scope: {{{../name}}}; use ../../ for grandparent{{else}} inside {{#each}}: {{#each fields.tags}}...{{else}}<p>No tags</p>{{/each}}Working with Assets working-with-assets
Assets referenced from Content Fragments are pre-rendered as HTML by AEM. Therefore, triple braces are mandatory for all asset references.
<img src="..." alt="..."><video> element<a href="..."> linkRemember:
- Always use triple braces for asset fields.
Using double braces will escape the generated HTML tag and display it as raw text rather than rendering the image, video, or link.
Asset field usage asset-field-usage
An example of asset field usage:
Custom template helpers customer-template-helpers
The system provides custom Handlebars helpers for generating HTML elements with custom HTML attributes. These helpers give you control over the generated markup, while handling the complexity of extracting source URLs from pre-rendered content.
Available helpers:
asset- Generates<img>tags with custom attributestext- Generates<span>tags wrapping text content with custom attributes
asset helper asset-helper
Syntax:
Remember:
- Use triple braces
{{{ }}}with the asset helper, not double braces!
Four basic examples four-basic-examples
Four basic examples are:
Supported Attributes supported-attributes
You can add any valid HTML attribute:
classclass="my-class another-class"idid="unique-id"altalt="Custom alt text" (overrides existing alt)data-*data-index="1" data-type="hero"aria-*aria-label="Description" aria-hidden="true"widthwidth="300"heightheight="200"loadingloading="lazy"stylestyle="border-radius: 8px;"Override Alt Text override-alt-text
The alt attribute from the original image can be overridden:
Complex example complex-example
A complex example is:
Using with loops using-with-loops
Asset helper in loops:
text helper text-helper
The text helper generates a <span> tag wrapping text content with custom CSS classes and HTML attributes. Useful for styling individual text fields.
Syntax:
Remember:
- Use triple braces
{{{ }}}with the text helper, not double braces!
Three Basic examples three-basic-examples
Three basic examples are:
Common Use Cases common-use-cases
Some common use cases include:
With loops with-loops
A common use case with loops includes:
Helpers - Attribute validation helpers-attribute-validation
Both helpers validate attribute names before including them in the output.
Valid attribute names:
-
Must start with a letter (a-z, A-Z)
-
Can only contain letters, digits, hyphens, and underscores; see the Naming Conventions
-
Case-insensitive
-
For example:
- Valid:
class,id,data-value,aria-label,my_attr,dataIndex1
- Invalid:
123-attr,-class,@special,$money
- Valid:
Invalid attribute names are silently skipped with a warning in the logs:
Remember:
- Check server logs for “Blocked invalid attribute name format” warnings.
Comparing direct output to helpers comparing-direct-output-to-helpers
When to ise direct output {{{fields.xxx}}}:
- You do not need custom styling
- You want the default output as-is
- The field contains complex HTML that you do not want to modify
When to use helpers:
- You need to add CSS classes for styling
- You need to add custom HTML attributes (
data-*,aria-*, and others) - You want consistent, controlled HTML structure
Comparison:
Quick reference quick-reference
Some quick reference information is provided for reference.
Context variables context-variables
The context variables:
Field access field-access
How to access fields:
Control flow control-flow
The control flow:
Loop variables loop-variables
The loop variables:
Custom template helpers custom-template-helpers
The custom template helpers:
Additional resources additional-resources
Additional resources are available: