CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

adobe/aem-cloud-service

All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

extending-core-components.mdskills/create-component/references/

Extending AEM Core Components

Complete guide for extending Adobe Core Components with proper inheritance patterns using Sling Resource Merger and Model Delegation.

Table of Contents

Project Component vs Core Component Extension

Not all component extension is the same. The approach depends on whether the parent component is an external Core Component or a component already in your project.

  • Core Component extension (what most of this reference covers): Uses Sling Resource Merger for dialog inheritance, @Self @Via(type = ResourceSuperType.class) for model delegation. Required because Core Components are external dependencies — you can't modify their source code directly.
  • Project component extension (simpler): When extending a component already in your project, you can directly extend the Java class (extends ParentModel), copy and modify the dialog (or use Sling Resource Merger), and inherit HTL via sling:resourceSuperType. No delegation pattern needed — just standard Java inheritance.

Decision Table

Parent ComponentDialog ApproachModel Approach
Core Component (under core/wcm/components/)Sling Resource Merger (overlay tabs/fields)@Self @Via(type = ResourceSuperType.class) delegation
Project component (under apps/[project]/components/)Direct copy + modify OR Sling Resource MergerDirect Java extends ParentModel

Key rule: Only use the @Self @Via(type = ResourceSuperType.class) delegation pattern when extending Core Components. For project components, use direct Java class extension (extends).

When to Extend vs Create New

ScenarioRecommendation
Need core functionality + custom fieldsExtend
Core component has 80%+ of what you needExtend
Need to hide some inherited tabs/fieldsExtend
Completely different behavior neededCreate new
Need to hide most inherited fieldsConsider Create new

Sling Resource Merger - Complete Reference

When extending a component via sling:resourceSuperType, the dialog is automatically inherited. Sling Resource Merger allows overlaying, hiding, and modifying inherited dialog content.

Property Reference

PropertyTypePurposeExample
sling:hideResourceBooleanHide entire node (tab, field, container)sling:hideResource="{Boolean}true"
sling:hideChildrenString[]Hide specific child nodes by namesling:hideChildren="[field1,field2]"
sling:hidePropertiesString[]Remove inherited properties before mergingsling:hideProperties="[fieldLabel,required]"
sling:orderBeforeStringPosition node before another siblingsling:orderBefore="existingTab"

Hide Entire Tab

To hide an inherited tab, add a node with the same name and sling:hideResource:

<!-- Hide "List Settings" tab inherited from core List component -->
<listSettings
    jcr:primaryType="nt:unstructured"
    sling:hideResource="{Boolean}true"/>

Hide Specific Field

<!-- Hide "maxItems" field within an inherited tab -->
<maxItems
    jcr:primaryType="nt:unstructured"
    sling:hideResource="{Boolean}true"/>

Hide Multiple Children

<!-- Hide multiple fields at once -->
<items
    jcr:primaryType="nt:unstructured"
    sling:hideChildren="[maxItems,orderBy,childDepth,tags]"/>

Override Inherited Field Properties

Use sling:hideProperties to remove specific inherited properties, then set new values:

<!-- Change inherited field label and make required -->
<title
    jcr:primaryType="nt:unstructured"
    sling:hideProperties="[fieldLabel,required,fieldDescription]"
    fieldLabel="Banner Title"
    fieldDescription="Enter the banner headline"
    required="{Boolean}true"/>

Position Custom Tab Before Inherited

<customProperties
    jcr:primaryType="nt:unstructured"
    jcr:title="Custom Properties"
    sling:orderBefore="listSettings"
    sling:resourceType="granite/ui/components/coral/foundation/container"
    margin="{Boolean}true">
    <items jcr:primaryType="nt:unstructured">
        <!-- Custom fields here -->
    </items>
</customProperties>

Core Component Dialog Tab Names Reference

To hide or modify tabs, you must use the exact node names from the parent component:

Core ComponentVersionTab Node Names
Listv4listSettings, itemSettings
Teaserv2properties, actions
Imagev3properties, features
Buttonv2properties
Titlev3properties
Textv2properties
Containerv1properties, policy
Tabsv1properties, accessibility
Accordionv1properties, accessibility
Carouselv1properties, accessibility
Navigationv2properties
Embedv2properties

Finding Tab Names

If you need to find tab names for a Core Component:

  1. Check Adobe Core Components GitHub: https://github.com/adobe/aem-core-wcm-components
  2. Navigate to: content/src/content/jcr_root/apps/core/wcm/components/{component}/{version}/_cq_dialog
  3. Look at the <items> nodes under <tabs>

Model Delegation Pattern

When to Use Delegation

ALWAYS delegate when extending Core Components that have:

Core ComponentHas Complex Logic?Use Delegation?Adaptable
ImageYes (renditions, lazy-load)YESSlingHttpServletRequest
TeaserYes (links, actions)YESSlingHttpServletRequest
ListYes (data fetching)YESSlingHttpServletRequest
ButtonYes (link handling)YESSlingHttpServletRequest
NavigationYes (page tree)YESSlingHttpServletRequest
TitleSimpleOptionalResource or SlingHttpServletRequest
TextSimpleOptionalResource or SlingHttpServletRequest
ContainerMediumRecommendedSlingHttpServletRequest

Why SlingHttpServletRequest?

Core Components use SlingHttpServletRequest as adaptable because:

  • Required for @Via(type = ResourceSuperType.class) delegation
  • Supports proper request context resolution
  • Enables JSON export via Sling Model Exporter
  • Provides access to request attributes and parameters

Delegation Template

package [package].models;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.annotations.via.ResourceSuperType;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;

@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = {CustomComponentModel.class, ComponentExporter.class},
    resourceType = CustomComponentModel.RESOURCE_TYPE,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
    name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
    extensions = ExporterConstants.SLING_MODEL_EXTENSION
)
public class CustomComponentModel implements ComponentExporter {

    public static final String RESOURCE_TYPE = "[project]/components/{component-name}";

    /**
     * Delegate to parent Core Component model via resourceSuperType.
     * Use fully qualified name to avoid import collisions.
     */
    @Self
    @Via(type = ResourceSuperType.class)
    private com.adobe.cq.wcm.core.components.models.ParentInterface coreComponent;

    /**
     * Access to current resource for reading child resources.
     */
    @SlingObject
    private Resource resource;

    /**
     * Custom property added by this extension.
     */
    @ValueMapValue
    private String customProperty;

    private List<CustomItem> customItems;

    @PostConstruct
    protected void init() {
        // Initialize custom multifield items
        customItems = new ArrayList<>();
        Resource itemsResource = resource.getChild("items");
        if (itemsResource != null) {
            for (Resource itemResource : itemsResource.getChildren()) {
                CustomItem item = itemResource.adaptTo(CustomItem.class);
                if (item != null && item.hasContent()) {
                    customItems.add(item);
                }
            }
        }
    }

    // Delegate inherited methods to parent
    public String getInheritedProperty() {
        return coreComponent != null ? coreComponent.getSomeProperty() : null;
    }

    // Custom property getters
    public String getCustomProperty() {
        return customProperty;
    }

    public List<CustomItem> getCustomItems() {
        return Collections.unmodifiableList(customItems);
    }

    public boolean hasContent() {
        return (customProperty != null && !customProperty.trim().isEmpty()) ||
               (customItems != null && !customItems.isEmpty()) ||
               coreComponent != null;
    }

    @Override
    public String getExportedType() {
        return RESOURCE_TYPE;
    }
}

Core Component Model Interfaces

Core ComponentInterface (Fully Qualified)
Listcom.adobe.cq.wcm.core.components.models.List
Teasercom.adobe.cq.wcm.core.components.models.Teaser
Imagecom.adobe.cq.wcm.core.components.models.Image
Buttoncom.adobe.cq.wcm.core.components.models.Button
Titlecom.adobe.cq.wcm.core.components.models.Title
Textcom.adobe.cq.wcm.core.components.models.Text
Navigationcom.adobe.cq.wcm.core.components.models.Navigation
Containercom.adobe.cq.wcm.core.components.models.LayoutContainer
Tabscom.adobe.cq.wcm.core.components.models.Tabs
Accordioncom.adobe.cq.wcm.core.components.models.Accordion
Carouselcom.adobe.cq.wcm.core.components.models.Carousel

Import Collision Resolution

Some Core Component interface names collide with java.util classes (e.g., List):

// WRONG - Import collision error
import java.util.List;
import com.adobe.cq.wcm.core.components.models.List;  // ERROR!

// CORRECT - Use fully qualified name for Core Component interface
import java.util.List;  // Keep for collections

// In field declaration, use fully qualified name:
@Self
@Via(type = ResourceSuperType.class)
private com.adobe.cq.wcm.core.components.models.List coreList;

ComponentExporter Interface

What is ComponentExporter?

ComponentExporter enables JSON serialization of Sling Models via the .model.json extension.

Required for:

  • SPA/headless implementations
  • JSON API endpoints
  • React/Angular AEM integrations

Implementation

// 1. Add to adapters in @Model
@Model(
    adapters = {CustomModel.class, ComponentExporter.class},  // Add ComponentExporter
    ...
)

// 2. Add @Exporter annotation
@Exporter(
    name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
    extensions = ExporterConstants.SLING_MODEL_EXTENSION
)

// 3. Implement interface
public class CustomModel implements ComponentExporter {

    // 4. Implement required method
    @Override
    public String getExportedType() {
        return RESOURCE_TYPE;  // Must match @Model resourceType
    }
}

For a complete worked example (CTA List extending Core List), unit testing patterns, and implementation checklists, see references/extending-core-components-example.md.

skills

README.md

tile.json