Skip to content

Content Fragments — AEM 6.5 On-Premise


1. Content Fragment Là Gì

Content Fragment (CF) là structured content tách biệt hoàn toàn khỏi presentation. Khác với page component (trộn content + layout), CF cho phép tái sử dụng cùng nội dung trên nhiều kênh: website, SPA, mobile app, email... thông qua Java API, REST, hoặc GraphQL.

CF được lưu dưới dạng dam:Asset trong /content/dam/.


2. Content Fragment Models

Model định nghĩa schema (fields + types) cho fragment. Được lưu trong configuration space /conf.

Bật CF Models cho một site

  1. Tools > General > Configuration Browser
  2. Chọn site config (VD: myproject)
  3. Tick Content Fragment Models
  4. Models được lưu tại: /conf/myproject/settings/dam/cfm/models/

Các kiểu field

Field TypeJCR StorageJava TypeKhi nào dùng
Single-line textStringStringTitle, label, text ngắn
Multi-line textStringStringRich text (HTML), Markdown, plain text
NumberLong / DoubleLong / DoubleSố lượng, giá, rating
BooleanBooleanBooleanToggle, flag
Date and TimeCalendarCalendarNgày publish, ngày event
EnumerationStringStringDropdown lựa chọn cố định
TagsString[]String[]AEM tag references
Content ReferenceStringStringPath tới page hoặc asset
Fragment ReferenceString[]String[]Tham chiếu tới CF khác
JSON ObjectString (JSON)StringDữ liệu cấu trúc tùy ý
Tab Placeholder----Nhóm visual trong editor, không chứa data

Cấu trúc JCR của Model

/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)

Validation trên Model

ValidationÁp dụng choHiệu quả
RequiredTất cảField bắt buộc phải có giá trị
Min/Max lengthText fieldsGiới hạn số ký tự
Min/Max valueNumber fieldsGiới hạn phạm vi số
UniqueText fieldsGiá trị phải duy nhất trong cùng model
Pattern (Regex)Text fieldsGiá trị phải match regex
AcceptContent/Fragment refGiới hạn model hoặc path có thể chọn

3. Cấu Trúc JCR Của Content Fragment

/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 = "..."

Data nằm tại jcr:content/data. Tên element trùng với tên field trong model. Fragment reference lưu dưới dạng String[] chứa path.

Variations

Variation là phiên bản thay thế của cùng nội dung (VD: bản tóm tắt, bản social media). Mỗi variation lưu riêng bộ field values:

/content/dam/myproject/articles/my-article
├── jcr:content
│   └── data
│       ├── master             ← variation mặc định
│       │   ├── title = "My Article Title"
│       │   └── body = "<p>Full article body...</p>"
│       └── summary            ← variation tùy chỉnh
│           ├── title = "My Article"
│           └── body = "<p>Short summary...</p>"

Variation kế thừa từ master. Chỉ các field được set tường minh trên variation mới được lưu riêng.


4. Đọc Content Fragment — Java API

Adapt Resource sang ContentFragment

Package chính: com.adobe.cq.dam.cfm.ContentFragment

java
@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) return;
        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");
        return bodyElement != null ? bodyElement.getContent() : null;
    }

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

ContentFragment API

MethodTrả vềMô tả
getTitle()StringTitle của fragment
getDescription()StringDescription
getName()StringNode name (URL-safe)
getElement(name)ContentElementTruy cập element theo tên field
getElements()Iterator<ContentElement>Iterate tất cả elements
getVariations()Iterator<ContentVariation>Liệt kê variations
hasElement(name)booleanKiểm tra element tồn tại
getAssociatedContent()Iterator<Resource>Associated content (collections)
adaptTo(Resource.class)ResourceUnderlying Sling resource

ContentElement API

MethodTrả vềMô tả
getName()StringTên field
getTitle()StringDisplay title
getContent()StringString value (cho text fields)
setContent(content, mimeType)voidSet content + MIME type
getValue()FragmentDataTyped value container
setValue(FragmentData)voidSet typed value
getContentType()StringMIME type (text/plain, text/html...)
getVariation(name)ContentVariationVariation cụ thể của element
getVariations()Iterator<ContentVariation>Liệt kê variations

Đọc typed values

java
// 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 (String[] chứa paths)
String[] refPaths = fragment.getElement("relatedArticles")
    .getValue().getValue(String[].class);

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

Đọc Variations

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

// Đọc variation cụ thể
ContentVariation summary = bodyElement.getVariation("summary");
if (summary != null) {
    String summaryBody = summary.getContent();
}

// Liệt kê tất cả variations
Iterator<ContentVariation> variations = bodyElement.getVariations();
while (variations.hasNext()) {
    ContentVariation v = variations.next();
    LOG.info("Variation: {} ({})", v.getTitle(), v.getName());
}

5. Tạo Content Fragment Programmatically

Tạo một fragment

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

    Resource modelResource = resolver.getResource(modelPath);
    if (modelResource == null) {
        throw new ContentFragmentException("Model not found: " + modelPath);
    }

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

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

    String nodeName = ResourceUtil.createUniqueChildName(folder, title);
    ContentFragment fragment = template.createFragment(folder, nodeName, title);

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

Set giá trị các kiểu field

java
/**
 * Text field (single-line hoặc multi-line).
 */
public void setText(ContentFragment fragment, String fieldName,
        String value, String mimeType) {
    fragment.getElement(fieldName).setContent(value, mimeType);
}

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

Sử dụng:

java
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());

Set Fragment References

java
public void setFragmentReferences(ContentFragment fragment, String fieldName,
        List<ContentFragment> references) {

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

    if (paths.length == 0) return;

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

Ví dụ hoàn chỉnh: tạo article + link references

java
public void createArticleWithRelated(ResourceResolver resolver) throws Exception {
    String articleModel = "/conf/myproject/settings/dam/cfm/models/article";
    String authorModel  = "/conf/myproject/settings/dam/cfm/models/author";

    // 1. Tạo author fragment
    ContentFragment author = createFragment(resolver, authorModel,
        "/content/dam/myproject/authors", "Jane Doe");
    setText(author, "name", "Jane Doe", "text/plain");
    setText(author, "bio", "Senior tech writer.", "text/plain");

    // 2. Tạo article chính
    ContentFragment article = createFragment(resolver, articleModel,
        "/content/dam/myproject/articles", "Getting Started with CF");
    setText(article, "title", "Getting Started with CF", "text/plain");
    setText(article, "body",
        "<p>Content Fragments manage structured content...</p>", "text/html");
    setTypedValue(article, "publishDate", Calendar.getInstance());
    setTypedValue(article, "featured", true);

    // 3. Link author → article
    setFragmentReferences(article, "author", Collections.singletonList(author));

    // 4. Tạo related articles
    List<ContentFragment> related = new ArrayList<>();
    for (String t : Arrays.asList("CF Models Deep Dive", "GraphQL Queries")) {
        ContentFragment cf = createFragment(resolver, articleModel,
            "/content/dam/myproject/articles", t);
        setText(cf, "title", t, "text/plain");
        related.add(cf);
    }

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

    // 6. Persist
    resolver.commit();
}

6. Cập Nhật Fragment Đã Tồn Tại

Update field values

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

// Text
fragment.getElement("title").setContent("Updated Title", "text/plain");

// Typed
FragmentData ratingData = fragment.getElement("rating").getValue();
ratingData.setValue(4L);
fragment.getElement("rating").setValue(ratingData);

resolver.commit();

Tạo và chỉnh sửa Variations

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

// Tạo variation mới
ContentVariation social = bodyElement.createVariation(
    "social", "Social Media", "Short version for social");
social.setContent("Check out our latest article!", "text/plain");

// Cập nhật variation có sẵn
ContentVariation existing = bodyElement.getVariation("summary");
if (existing != null) {
    existing.setContent("Updated summary text", "text/plain");
}

resolver.commit();

Xóa fragment

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

7. Render Content Fragment Trong HTL

Core Content Fragment Component

AEM Core Components cung cấp sẵn Content Fragment component. Thêm vào template policy, cấu hình qua dialog.

Custom Sling Model + HTL

Sling Model:

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) return;
        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();
    }
}

HTL template:

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>

Rich text cần context='html' để không bị escape:

html
<div>${model.body @ context='html'}</div>

8. Query Content Fragments Bằng QueryBuilder

Tìm tất cả CF thuộc model "article", field featured = true, sắp xếp theo publishDate:

java
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");
predicates.put("p.guessTotal", "true");

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

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

Lưu ý query CF:

  • Filter theo model: jcr:content/data/cq:model
  • Field values nằm tại: jcr:content/data/master/<fieldName>
  • Luôn set p.limit hợp lý, tránh p.limit=-1
  • Tạo Oak index cho cq:model và các custom properties hay query

9. Assets HTTP API (REST)

AEM 6.5 hỗ trợ REST API để CRUD fragment mà không cần Java code. Dùng cho external systems hoặc migration scripts.

bash
# Đọc fragment
curl -u admin:admin \
  "http://localhost:4502/api/assets/myproject/articles/my-article.json"

# Tạo 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"

10. Folder Structure Chuẩn

/content/dam/myproject/
├── articles/               # Article fragments
│   ├── 2025/               # Tổ chức theo năm (tùy chọn)
│   └── 2026/
├── authors/                # Author fragments
├── categories/             # Category fragments (nếu dùng CF-based taxonomy)
├── config/                 # Configuration fragments (feature flags, settings)
└── shared/                 # Shared fragments (CTAs, disclaimers, footers)

Tạo folder programmatically

java
public Resource ensureFolder(ResourceResolver resolver, String path)
        throws PersistenceException {
    Resource existing = resolver.getResource(path);
    if (existing != null) return existing;

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

    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);
}

11. Bulk Operations

Groovy Console — update field trên tất cả fragments cùng model

groovy
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) return

    def model = child.getChild("jcr:content/data")?.valueMap?.get("cq:model", "")
    if (model != modelPath) return

    cf.getElement("category").setContent("technology", "text/plain")
    count++
}

if (count > 0) {
    resourceResolver.commit()
}
println "Updated ${count} fragments"

Bulk export to JSON

java
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;

        Resource dataNode = child.getChild("jcr:content/data");
        if (dataNode == null) continue;

        String cfModel = dataNode.getValueMap().get("cq:model", "");
        if (!modelPath.equals(cfModel)) continue;

        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 el = elements.next();
            entry.put(el.getName(), el.getContent());
        }
        results.add(entry);
    }
    return results;
}

12. Best Practices

Model design

  • Một model cho một content type (Article, Author, FAQ). Tránh model "catch-all" nhiều chục field.
  • Dùng Fragment Reference để link data liên quan, không duplicate.
  • Đặt tên field nhất quán: camelCase, match API contract.
  • Thêm description cho field — hiển thị dưới dạng help text trong editor.
  • Thêm field an toàn. Rename/remove field sẽ break fragments hiện có.

Authoring

  • Tổ chức fragments theo folder: type, year, hoặc locale.
  • Dùng Variations cho channel-specific content (web, email, social) thay vì tạo fragment riêng.
  • Tag fragments để hỗ trợ tìm kiếm cross-model.
  • Dùng Associated Content để link DAM assets thuộc về fragment.

Performance

  • Query luôn filter theo jcr:content/data/cq:model trước.
  • Tạo Oak index cho các fragment properties hay query.
  • Cache fragment data trong Sling Model bằng @PostConstruct — không đọc lại JCR mỗi lần gọi getter.

13. Lỗi Thường Gặp

LỗiNguyên nhân & giải pháp
adaptTo(ContentFragment.class) trả về nullResource phải là dam:Asset có tham chiếu model hợp lệ. Kiểm tra jcr:content/data/cq:model.
getElement() trả về nullTên field phải match chính xác model (case-sensitive).
Rich text hiển thị raw HTMLDùng context='html' trong HTL: ${model.body @ context='html'}
Fragment reference bị hỏngReferences lưu dưới dạng path. Nếu target bị move, reference trỏ sai.
Query CF chậmTạo Oak index cho property cq:model và các custom filter properties.
Variation content rỗngVariation kế thừa từ master. Chỉ field được set tường minh mới có data riêng.

Tham Khảo

AEM 6.5 On-Premise Developer Notes