Skip to main content

Content Fragments

Content Fragments are channel-neutral, structured content in AEM. Unlike page components that mix content with presentation, Content Fragments separate content from layout, making the same content reusable across websites, mobile apps, SPAs, email, and any other channel via GraphQL, REST, or direct Java API access.

Content Fragment Models

A Content Fragment Model defines the schema (fields and their types) for a fragment. Models are created in the AEM configuration space (/conf) and must be enabled per site configuration.

Enabling CF Models for a site

  1. Navigate to Tools > General > Configuration Browser
  2. Select your site configuration (e.g., myproject)
  3. Enable Content Fragment Models
  4. Models are stored at /conf/myproject/settings/dam/cfm/models/

Available field types

Field TypeJCR StorageJava TypeUse case
Single-line textStringStringTitles, labels, short text
Multi-line textStringStringRich text (HTML), Markdown, or plain text
NumberLong / DoubleLong / DoubleQuantities, prices, ratings
BooleanBooleanBooleanToggles, flags
Date and TimeCalendarCalendarPublish dates, event dates
EnumerationStringStringPredefined choices (dropdown)
TagsString[]String[]AEM tag references
Content ReferenceStringStringPath to a page or asset
Fragment ReferenceString[]String[]References to other Content Fragments
JSON ObjectString (JSON)StringArbitrary structured data
Tab Placeholder----Visual grouping in the editor (no data)

Model JCR structure

/conf/myproject/settings/dam/cfm/models/article
├── jcr:content
│ ├── jcr:title = "Article"
│ ├── jcr:description = "An article with title, body, and author"
│ └── model
│ ├── cq:fields
│ │ ├── title (fieldType: "text-single", required: true)
│ │ ├── body (fieldType: "text-multi", mimeType: "text/html")
│ │ ├── publishDate (fieldType: "calendar")
│ │ ├── category (fieldType: "enumeration", options: [...])
│ │ ├── featuredImage (fieldType: "content-reference")
│ │ ├── author (fieldType: "fragment-reference", modelPath: "/conf/.../author")
│ │ └── relatedArticles (fieldType: "fragment-reference", multiple: true)

Model field validation

Models support built-in validation rules:

ValidationApplies toEffect
RequiredAll typesField must have a value
Min/Max lengthText fieldsCharacter count limits
Min/Max valueNumber fieldsNumeric range
UniqueText fieldsValue must be unique across fragments of this model
Pattern (Regex)Text fieldsValue must match the regex
Accept (for references)Content/Fragment referencesLimits selectable models or paths

Content Fragment Structure in the JCR

Content Fragments are stored as dam:Asset nodes under /content/dam/. Understanding the JCR structure is essential for programmatic access.

/content/dam/myproject/articles/my-article
├── jcr:content
│ ├── jcr:primaryType = "dam:AssetContent"
│ ├── data
│ │ ├── cq:model = "/conf/myproject/settings/dam/cfm/models/article"
│ │ ├── title = "My Article Title"
│ │ ├── body = "<p>The article body in HTML...</p>"
│ │ ├── publishDate = "2025-06-15T10:00:00.000+02:00"
│ │ ├── category = "technology"
│ │ ├── featuredImage = "/content/dam/myproject/images/hero.jpg"
│ │ └── author = ["/content/dam/myproject/authors/john-doe"]
│ └── metadata
│ ├── dc:title = "My Article Title"
│ └── dc:description = "..."
info

Content Fragment data lives under jcr:content/data. The element names match the field names defined in the model. Fragment references are stored as String arrays of paths.

Variations

Content Fragments support variations -- alternative versions of the same content (e.g., a short summary, a social media version). Each variation stores its own set of field values:

/content/dam/myproject/articles/my-article
├── jcr:content
│ ├── data
│ │ ├── master (default variation)
│ │ │ ├── title = "My Article Title"
│ │ │ └── body = "<p>Full article body...</p>"
│ │ └── summary (custom variation)
│ │ ├── title = "My Article"
│ │ └── body = "<p>Short summary...</p>"

Reading Content Fragments (Java API)

Adapting a resource to ContentFragment

The primary API is com.adobe.cq.dam.cfm.ContentFragment:

Reading a fragment in a Sling Model
@Model(
adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ArticleModel {

@ValueMapValue
private String fragmentPath;

@SlingObject
private ResourceResolver resolver;

private ContentFragment fragment;

@PostConstruct
protected void init() {
if (fragmentPath != null) {
Resource cfResource = resolver.getResource(fragmentPath);
if (cfResource != null) {
fragment = cfResource.adaptTo(ContentFragment.class);
}
}
}

public String getTitle() {
if (fragment == null) return null;
return fragment.getElement("title").getContent();
}

public String getBody() {
if (fragment == null) return null;
ContentElement bodyElement = fragment.getElement("body");
// getContent() returns the raw value; for rich text this is HTML
return bodyElement != null ? bodyElement.getContent() : null;
}

public Calendar getPublishDate() {
if (fragment == null) return null;
FragmentData data = fragment.getElement("publishDate").getValue();
return data != null ? data.getValue(Calendar.class) : null;
}
}

ContentFragment API reference

MethodReturnsDescription
getTitle()StringFragment title
getDescription()StringFragment description
getName()StringNode name (URL-safe)
getElement(name)ContentElementAccess a specific element by field name
getElements()Iterator<ContentElement>Iterate over all elements
getVariations()Iterator<ContentVariation>List available variations
hasElement(name)booleanCheck if an element exists
getAssociatedContent()Iterator<Resource>Get associated content (collections)
adaptTo(Resource.class)ResourceGet the underlying Sling resource

ContentElement API reference

MethodReturnsDescription
getName()StringElement/field name
getTitle()StringDisplay title
getContent()StringString value (for text fields)
setContent(content, mimeType)voidSet content with MIME type
getValue()FragmentDataTyped value container
setValue(FragmentData)voidSet typed value
getContentType()StringMIME type (text/plain, text/html, etc.)
getVariation(name)ContentVariationGet a specific variation of this element
getVariations()Iterator<ContentVariation>List variations

Reading typed values

The FragmentData object provides type-safe access to field values:

// String
String title = fragment.getElement("title").getContent();

// Number
FragmentData numberData = fragment.getElement("rating").getValue();
Long rating = numberData.getValue(Long.class);
Double price = fragment.getElement("price").getValue().getValue(Double.class);

// Boolean
Boolean featured = fragment.getElement("featured").getValue().getValue(Boolean.class);

// Date
Calendar date = fragment.getElement("publishDate").getValue().getValue(Calendar.class);

// Fragment references (stored as String array of paths)
FragmentData refData = fragment.getElement("relatedArticles").getValue();
String[] refPaths = refData.getValue(String[].class);

// Content reference (single path)
String imagePath = fragment.getElement("featuredImage").getContent();

Reading variations

// Read the "summary" variation of the body element
ContentElement bodyElement = fragment.getElement("body");
ContentVariation summaryVariation = bodyElement.getVariation("summary");

if (summaryVariation != null) {
String summaryBody = summaryVariation.getContent();
}

// List all available variations
Iterator<ContentVariation> variations = bodyElement.getVariations();
while (variations.hasNext()) {
ContentVariation variation = variations.next();
LOG.info("Variation: {} ({})", variation.getTitle(), variation.getName());
}

Creating Content Fragments Programmatically

Creating a single fragment

core/.../services/impl/FragmentServiceImpl.java
package com.myproject.core.services.impl;

import com.adobe.cq.dam.cfm.ContentFragment;
import com.adobe.cq.dam.cfm.ContentFragmentException;
import com.adobe.cq.dam.cfm.FragmentTemplate;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public ContentFragment createFragment(
ResourceResolver resolver,
String modelPath,
String folderPath,
String title) throws ContentFragmentException {

// 1. Resolve the CF model
Resource modelResource = resolver.getResource(modelPath);
if (modelResource == null) {
throw new ContentFragmentException("Model not found: " + modelPath);
}

// 2. Adapt to FragmentTemplate
FragmentTemplate template = modelResource.adaptTo(FragmentTemplate.class);
if (template == null) {
throw new ContentFragmentException("Cannot adapt model to FragmentTemplate: " + modelPath);
}

// 3. Resolve the target folder
Resource folder = resolver.getResource(folderPath);
if (folder == null) {
throw new ContentFragmentException("Folder not found: " + folderPath);
}

// 4. Generate a unique node name from the title
String nodeName = ResourceUtil.createUniqueChildName(folder, title);

// 5. Create the fragment
ContentFragment fragment = template.createFragment(folder, nodeName, title);

LOG.info("Created Content Fragment: {} ({})", title, fragment.adaptTo(Resource.class).getPath());
return fragment;
}

Setting field values

Setting different field types
/**
* Set a text field (single-line or multi-line).
*/
public void setText(ContentFragment fragment, String fieldName, String value, String mimeType) {
fragment.getElement(fieldName).setContent(value, mimeType);
}

/**
* Set a typed value (number, boolean, date, etc.).
*/
public void setTypedValue(ContentFragment fragment, String fieldName, Object value) {
FragmentData data = fragment.getElement(fieldName).getValue();
data.setValue(value);
fragment.getElement(fieldName).setValue(data);
}

// Usage:
setText(fragment, "title", "My Article", "text/plain");
setText(fragment, "body", "<p>Rich text content</p>", "text/html");
setTypedValue(fragment, "rating", 5L);
setTypedValue(fragment, "featured", true);
setTypedValue(fragment, "publishDate", Calendar.getInstance());

Setting fragment references

Linking fragments together
/**
* Set a fragment reference field (single or multi-valued).
*/
public void setFragmentReferences(ContentFragment fragment, String fieldName,
List<ContentFragment> references) {

List<ContentFragment> validRefs = references.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());

if (validRefs.isEmpty()) {
return;
}

String[] paths = validRefs.stream()
.map(ref -> ref.adaptTo(Resource.class).getPath())
.toArray(String[]::new);

FragmentData data = fragment.getElement(fieldName).getValue();
data.setValue(paths);
fragment.getElement(fieldName).setValue(data);
}

Complete creation example

Creating a parent fragment with linked children
public void createArticleWithRelated(ResourceResolver resolver) throws Exception {

String modelPath = "/conf/myproject/settings/dam/cfm/models/article";
String authorModelPath = "/conf/myproject/settings/dam/cfm/models/author";
String folderPath = "/content/dam/myproject/articles";

// 1. Create the author fragment
ContentFragment author = createFragment(resolver, authorModelPath,
"/content/dam/myproject/authors", "Jane Doe");
setText(author, "name", "Jane Doe", "text/plain");
setText(author, "bio", "Senior tech writer with 10 years of experience.", "text/plain");

// 2. Create the main article
ContentFragment article = createFragment(resolver, modelPath,
folderPath, "Getting Started with Content Fragments");

setText(article, "title", "Getting Started with Content Fragments", "text/plain");
setText(article, "body",
"<p>Content Fragments are a powerful way to manage structured content...</p>",
"text/html");
setTypedValue(article, "publishDate", Calendar.getInstance());
setTypedValue(article, "featured", true);

// 3. Link author to article
setFragmentReferences(article, "author", List.of(author));

// 4. Create related articles
List<ContentFragment> relatedArticles = new ArrayList<>();
for (String relatedTitle : List.of("CF Models Deep Dive", "GraphQL Queries")) {
ContentFragment related = createFragment(resolver, modelPath,
folderPath, relatedTitle);
setText(related, "title", relatedTitle, "text/plain");
relatedArticles.add(related);
}

// 5. Link related articles
setFragmentReferences(article, "relatedArticles", relatedArticles);

// 6. Commit all changes
resolver.commit();
}

Modifying Existing Fragments

Updating field values

Resource cfResource = resolver.getResource("/content/dam/myproject/articles/my-article");
ContentFragment fragment = cfResource.adaptTo(ContentFragment.class);

// Update a text field
fragment.getElement("title").setContent("Updated Title", "text/plain");

// Update a typed field
FragmentData ratingData = fragment.getElement("rating").getValue();
ratingData.setValue(4L);
fragment.getElement("rating").setValue(ratingData);

// Don't forget to commit
resolver.commit();

Creating and editing variations

ContentElement bodyElement = fragment.getElement("body");

// Create a new variation
ContentVariation socialVariation = bodyElement.createVariation("social", "Social Media", "Short version for social");

// Set variation content
socialVariation.setContent("Check out our latest article on Content Fragments!", "text/plain");

// Update an existing variation
ContentVariation existingVariation = bodyElement.getVariation("summary");
if (existingVariation != null) {
existingVariation.setContent("Updated summary text", "text/plain");
}

resolver.commit();

Deleting fragments

Resource cfResource = resolver.getResource("/content/dam/myproject/articles/old-article");
if (cfResource != null) {
resolver.delete(cfResource);
resolver.commit();
}

Content Fragment in HTL Components

You can render Content Fragments in HTL components using either the built-in Content Fragment component or a custom Sling Model.

Using the Core Component

The AEM Core Content Fragment component renders Content Fragments out of the box. Add it to your template policy and configure via dialog.

Custom HTL rendering

core/.../models/ArticleCardModel.java
@Model(
adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ArticleCardModel {

@ValueMapValue
private String fragmentPath;

@SlingObject
private ResourceResolver resolver;

private ContentFragment fragment;

@PostConstruct
protected void init() {
if (fragmentPath != null) {
Resource cfResource = resolver.getResource(fragmentPath);
if (cfResource != null) {
fragment = cfResource.adaptTo(ContentFragment.class);
}
}
}

public String getTitle() {
return getElementContent("title");
}

public String getSummary() {
return getElementContent("summary");
}

public String getImagePath() {
return getElementContent("featuredImage");
}

public boolean isEmpty() {
return fragment == null;
}

private String getElementContent(String name) {
if (fragment == null || !fragment.hasElement(name)) {
return null;
}
return fragment.getElement(name).getContent();
}
}
article-card.html
<sly data-sly-use.model="com.myproject.core.models.ArticleCardModel"/>
<div data-sly-test="${!model.empty}" class="article-card">
<img data-sly-test="${model.imagePath}" src="${model.imagePath}" alt="${model.title}"/>
<h3>${model.title}</h3>
<p>${model.summary}</p>
</div>
<div data-sly-test="${model.empty}" class="cq-placeholder">
Select a Content Fragment
</div>

Querying Content Fragments

QueryBuilder

Find Content Fragments of a specific model:

Find all articles published in 2025
Map<String, String> predicates = new HashMap<>();
predicates.put("path", "/content/dam/myproject");
predicates.put("type", "dam:Asset");
predicates.put("1_property", "jcr:content/data/cq:model");
predicates.put("1_property.value", "/conf/myproject/settings/dam/cfm/models/article");
predicates.put("2_property", "jcr:content/data/master/featured");
predicates.put("2_property.value", "true");
predicates.put("orderby", "@jcr:content/data/master/publishDate");
predicates.put("orderby.sort", "desc");
predicates.put("p.limit", "10");

Query query = queryBuilder.createQuery(PredicateGroup.create(predicates), session);
SearchResult result = query.getResult();

List<ContentFragment> fragments = new ArrayList<>();
for (Hit hit : result.getHits()) {
Resource resource = hit.getResource();
ContentFragment cf = resource.adaptTo(ContentFragment.class);
if (cf != null) {
fragments.add(cf);
}
}

JCR-SQL2

Find fragments by model and text search
String sql2 = "SELECT * FROM [dam:Asset] AS a "
+ "WHERE ISDESCENDANTNODE(a, '/content/dam/myproject') "
+ "AND a.[jcr:content/data/cq:model] = '/conf/myproject/settings/dam/cfm/models/article' "
+ "AND CONTAINS(a.[jcr:content/data/master/title], 'AEM') "
+ "ORDER BY a.[jcr:content/data/master/publishDate] DESC";

Session session = resolver.adaptTo(Session.class);
QueryManager qm = session.getWorkspace().getQueryManager();
javax.jcr.query.Query query = qm.createQuery(sql2, javax.jcr.query.Query.JCR_SQL2);
query.setLimit(20);

QueryResult result = query.execute();
NodeIterator nodes = result.getNodes();
while (nodes.hasNext()) {
Node node = nodes.nextNode();
Resource resource = resolver.getResource(node.getPath());
ContentFragment cf = resource.adaptTo(ContentFragment.class);
// ... process fragment ...
}

GraphQL (for external consumers)

Content Fragments are the primary data source for AEM's built-in GraphQL API. See the dedicated Headless GraphQL page for endpoint setup, queries, and configuration.

Example persisted query:

# Persisted query: /content/cq:graphql/myproject/endpoint/articles
{
articleList(
filter: {
featured: { _expressions: [{ value: true }] }
}
_sort: "publishDate"
_limit: 10
) {
items {
_path
title
body { html }
publishDate
featuredImage { ... on ImageRef { _path } }
author { name bio }
}
}
}

Content Fragment Folder Structure

A well-organized DAM folder structure makes Content Fragments easier to manage, query, and maintain permissions on:

/content/dam/myproject/
├── articles/ # Article fragments
│ ├── 2025/ # Organised by year (optional)
│ │ ├── getting-started
│ │ └── advanced-topics
│ └── 2026/
├── authors/ # Author fragments
│ ├── jane-doe
│ └── john-smith
├── categories/ # Category fragments (if using CF-based taxonomy)
├── config/ # Configuration fragments (feature flags, settings)
└── shared/ # Shared fragments (CTAs, disclaimers, footers)

Creating folder structure programmatically

Ensuring a folder hierarchy exists
public Resource ensureFolder(ResourceResolver resolver, String path) throws PersistenceException {
Resource existing = resolver.getResource(path);
if (existing != null) {
return existing;
}

// Recursively create parent folders
String parentPath = path.substring(0, path.lastIndexOf('/'));
Resource parent = ensureFolder(resolver, parentPath);

// Create the folder as a sling:OrderedFolder (standard for DAM)
Map<String, Object> props = new HashMap<>();
props.put("jcr:primaryType", "sling:OrderedFolder");
props.put("jcr:title", path.substring(path.lastIndexOf('/') + 1));

return resolver.create(parent, path.substring(path.lastIndexOf('/') + 1), props);
}

Content Fragment Bulk Operations

Bulk update with Groovy Console

Update a field on all fragments of a model
import com.adobe.cq.dam.cfm.ContentFragment

def modelPath = "/conf/myproject/settings/dam/cfm/models/article"
def targetFolder = "/content/dam/myproject/articles"
def count = 0

getResource(targetFolder).listChildren().each { child ->
def cf = child.adaptTo(ContentFragment.class)
if (cf != null) {
def model = child.getChild("jcr:content/data")?.valueMap?.get("cq:model", "")
if (model == modelPath) {
// Update a field
cf.getElement("category").setContent("technology", "text/plain")
count++
}
}
}

if (count > 0) {
resourceResolver.commit()
}

println "Updated ${count} fragments"

Bulk export to JSON

Export all fragments of a model as JSON
public List<Map<String, Object>> exportFragments(ResourceResolver resolver,
String folderPath, String modelPath) {

List<Map<String, Object>> results = new ArrayList<>();

Resource folder = resolver.getResource(folderPath);
if (folder == null) return results;

for (Resource child : folder.getChildren()) {
ContentFragment cf = child.adaptTo(ContentFragment.class);
if (cf == null) continue;

// Check model match
Resource dataNode = child.getChild("jcr:content/data");
if (dataNode == null) continue;
String cfModel = dataNode.getValueMap().get("cq:model", "");
if (!modelPath.equals(cfModel)) continue;

// Build export map
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("path", child.getPath());
entry.put("title", cf.getTitle());

Iterator<ContentElement> elements = cf.getElements();
while (elements.hasNext()) {
ContentElement element = elements.next();
entry.put(element.getName(), element.getContent());
}

results.add(entry);
}

return results;
}

AEMaaCS Considerations

TopicDetails
Content Fragment APISame API as AEM 6.5; the com.adobe.cq.dam.cfm package is fully available
GraphQLBuilt-in and enabled by default on AEMaaCS; requires Dispatcher configuration for caching
Persisted queriesPreferred over ad-hoc queries for production use; cacheable by Dispatcher/CDN
Assets HTTP APIREST API for CRUD operations on fragments without Java code
CF ConsoleAEMaaCS has a dedicated Content Fragments Console at /ui#/aem/cf/admin/
OpenAPIAEMaaCS supports the Content Fragments OpenAPI for headless delivery
Bulk importsUse the Content Fragment Migration tool or the Assets HTTP API for large imports

Assets HTTP API (REST)

For external systems that need to create or read fragments without Java:

# Read a fragment
curl -u admin:admin \
"http://localhost:4502/api/assets/myproject/articles/my-article.json"

# Create a fragment
curl -u admin:admin \
-X POST \
-H "Content-Type: application/json" \
-d '{
"properties": {
"cq:model": "/conf/myproject/settings/dam/cfm/models/article",
"elements": {
"title": { "value": "New Article" },
"body": { "value": "<p>Content here</p>", "contentType": "text/html" }
}
}
}' \
"http://localhost:4502/api/assets/myproject/articles/new-article"

Best Practices

Model design

  • Keep models focused -- one model per content type (Article, Author, FAQ). Avoid catch-all models with dozens of fields
  • Use fragment references to link related content rather than duplicating data
  • Name fields consistently -- use camelCase, match your API contract (GraphQL field names derive from model field names)
  • Add descriptions to fields -- they appear as help text in the author UI
  • Version your models carefully -- adding fields is safe; renaming or removing fields breaks existing fragments

Authoring workflow

  • Organise fragments in logical folders by type, year, or locale
  • Use variations for channel-specific content (web, email, social) rather than creating separate fragments
  • Tag fragments to enable cross-model discovery and filtering
  • Use associated content to link DAM assets that belong to a fragment

Performance

  • Query by model path (jcr:content/data/cq:model) as the primary filter to limit results
  • Create Oak indexes for frequently queried fragment properties
  • Cache fragment data in Sling Models with @PostConstruct rather than reading on every getter call
  • Use persisted GraphQL queries on Publish -- they are cacheable by Dispatcher and CDN

Common pitfalls

PitfallSolution
Fragment returns null when adaptedThe resource must be a dam:Asset with a valid CF model reference
getElement() returns nullThe field name must match the model exactly (case-sensitive)
Rich text displays raw HTMLUse context='html' in HTL: ${model.body @ context='html'}
Fragment references not resolvingReferences are stored as paths; if the target is moved, the reference breaks
Slow fragment queriesAdd an Oak index for the cq:model property and your custom filter properties
Variation content is emptyVariations inherit from master; only fields explicitly set on a variation are stored
CORS errors on GraphQLConfigure CORS and referrer filters; see Headless GraphQL

See also