> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# Context-Aware Configuration

**Sling Context-Aware Configuration** (CA Config) lets you store configuration values in the content tree so they resolve differently depending on the content context. Unlike OSGi configurations (which are global or per-run-mode), CA Config values are **site-specific, tenant-specific, or even page-specific** - without any code branching.

Use cases:

- Different analytics tracking IDs per site
- API keys that vary between tenants
- Feature flags scoped to a specific brand
- Social media URLs per regional site <svg id="mermaid-svg-8306692" width="100%" xmlns="http://www.w3.org/2000/svg" style="max-width: 276px;" viewBox="0 0 276 834" role="graphics-document document" aria-roledescription="flowchart-v2"><g><marker id="mermaid-svg-8306692_flowchart-v2-pointEnd" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-pointStart" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-pointEnd-margin" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" style="stroke-width: 0; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-pointStart-margin" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" style="stroke-width: 0; stroke-dasharray: 1, 0;"></polygon></marker><marker id="mermaid-svg-8306692_flowchart-v2-circleEnd" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-8306692_flowchart-v2-circleStart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-8306692_flowchart-v2-circleEnd-margin" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-8306692_flowchart-v2-circleStart-margin" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" style="stroke-width: 0; stroke-dasharray: 1, 0;"></circle></marker><marker id="mermaid-svg-8306692_flowchart-v2-crossEnd" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-crossStart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-crossEnd-margin" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5;"></path></marker><marker id="mermaid-svg-8306692_flowchart-v2-crossStart-margin" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"></path></marker><g><g></g><g><path d="M138,106L138,113.833C138,121.667,138,137.333,138,152.333C138,167.333,138,181.667,138,188.833L138,196" id="mermaid-svg-8306692-L_Page_SiteRoot_0" style=";" data-edge="true" data-et="edge" data-id="L_Page_SiteRoot_0" data-points="W3sieCI6MTM4LCJ5IjoxMDZ9LHsieCI6MTM4LCJ5IjoxNTN9LHsieCI6MTM4LCJ5IjoyMDB9XQ==" data-look="classic" marker-end="url(#mermaid-svg-8306692_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M138,274L138,283.833C138,293.667,138,313.333,138,332.333C138,351.333,138,369.667,138,378.833L138,388" id="mermaid-svg-8306692-L_SiteRoot_Conf_0" style=";" data-edge="true" data-et="edge" data-id="L_SiteRoot_Conf_0" data-points="W3sieCI6MTM4LCJ5IjoyNzR9LHsieCI6MTM4LCJ5IjozMzN9LHsieCI6MTM4LCJ5IjozOTJ9XQ==" data-look="classic" marker-end="url(#mermaid-svg-8306692_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M138,490L138,497.833C138,505.667,138,521.333,138,536.333C138,551.333,138,565.667,138,572.833L138,580" id="mermaid-svg-8306692-L_Conf_Global_0" style=";" data-edge="true" data-et="edge" data-id="L_Conf_Global_0" data-points="W3sieCI6MTM4LCJ5Ijo0OTB9LHsieCI6MTM4LCJ5Ijo1Mzd9LHsieCI6MTM4LCJ5Ijo1ODR9XQ==" data-look="classic" marker-end="url(#mermaid-svg-8306692_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path><path d="M138,658L138,665.833C138,673.667,138,689.333,138,704.333C138,719.333,138,733.667,138,740.833L138,748" id="mermaid-svg-8306692-L_Global_Default_0" style=";" data-edge="true" data-et="edge" data-id="L_Global_Default_0" data-points="W3sieCI6MTM4LCJ5Ijo2NTh9LHsieCI6MTM4LCJ5Ijo3MDV9LHsieCI6MTM4LCJ5Ijo3NTJ9XQ==" data-look="classic" marker-end="url(#mermaid-svg-8306692_flowchart-v2-pointEnd)" fill="none" stroke="currentColor"></path></g><g><g transform="translate(138, 153)"><g data-id="L_Page_SiteRoot_0" transform="translate(-35.046875, -22)"><foreignObject width="70.09375" height="44"><p>1. Lookup</p></foreignObject></g></g><g transform="translate(138, 333)"><g data-id="L_SiteRoot_Conf_0" transform="translate(-100, -34)"><foreignObject width="200" height="68"><p>2. sling:configRef → /conf/brand-a</p></foreignObject></g></g><g transform="translate(138, 537)"><g data-id="L_Conf_Global_0" transform="translate(-80.09896087646484, -22)"><foreignObject width="160.1979217529297" height="44"><p>3. Not found? Fallback</p></foreignObject></g></g><g transform="translate(138, 705)"><g data-id="L_Global_Default_0" transform="translate(-80.09896087646484, -22)"><foreignObject width="160.1979217529297" height="44"><p>4. Not found? Fallback</p></foreignObject></g></g></g><g><g id="mermaid-svg-8306692-flowchart-Page-0" data-look="classic" transform="translate(138, 57)"><rect style="" x="-130" y="-49" width="260" height="98" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-100, -34)"><rect></rect><foreignObject width="200" height="68"><p>/content/brand-a/en/home</p></foreignObject></g></g><g id="mermaid-svg-8306692-flowchart-SiteRoot-1" data-look="classic" transform="translate(138, 237)"><rect style="" x="-93.83333587646484" y="-37" width="187.6666717529297" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-63.833335876464844, -22)"><rect></rect><foreignObject width="127.66667175292969" height="44"><p>/content/brand-a</p></foreignObject></g></g><g id="mermaid-svg-8306692-flowchart-Conf-2" data-look="classic" transform="translate(138, 441)"><rect style="" x="-130" y="-49" width="260" height="98" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-100, -34)"><rect></rect><foreignObject width="200" height="68"><p>/conf/brand-a/sling:configs</p></foreignObject></g></g><g id="mermaid-svg-8306692-flowchart-Global-3" data-look="classic" transform="translate(138, 621)"><rect style="" x="-124.17708587646484" y="-37" width="248.3541717529297" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-94.17708587646484, -22)"><rect></rect><foreignObject width="188.3541717529297" height="44"><p>/conf/global/sling:configs</p></foreignObject></g></g><g id="mermaid-svg-8306692-flowchart-Default-4" data-look="classic" transform="translate(138, 789)"><rect style="" x="-101.05729675292969" y="-37" width="202.11459350585938" height="74" fill="none" stroke="currentColor"></rect><g style="" transform="translate(-71.05729675292969, -22)"><rect></rect><foreignObject width="142.11459350585938" height="44"><p>Code default values</p></foreignObject></g></g></g></g></g><defs></defs><defs></defs><linearGradient id="mermaid-svg-8306692-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#cccccc" stop-opacity="1"></stop><stop offset="100%" stop-color="hsl(180, 0%, 18.3529411765%)" stop-opacity="1"></stop></linearGradient></svg>

***

## How It Works

### The lookup chain

When code requests a CA Config value for a given resource, Sling walks up the content tree looking for a `sling:configRef` property. This property points to a `/conf/...` path where the configuration is stored.

1. Start at the resource (e.g., `/content/brand-a/en/products/shoe`)
2. Walk up: `/content/brand-a/en/products` → `/content/brand-a/en` → `/content/brand-a`
3. Find `sling:configRef = /conf/brand-a` on `/content/brand-a`
4. Look for the configuration under `/conf/brand-a/sling:configs/`
5. If not found, check `/conf/global/sling:configs/` (if configured)
6. If still not found, use the default values from the annotation

### JCR structure

```markdown
/conf/
├── brand-a/
│   └── sling:configs/
│       └── com.myproject.core.config.SiteConfig/
│           ├── analyticsId = "UA-12345-A"
│           ├── socialFacebookUrl = "https://facebook.com/brand-a"
│           └── enableNewHeader = true
├── brand-b/
│   └── sling:configs/
│       └── com.myproject.core.config.SiteConfig/
│           ├── analyticsId = "UA-12345-B"
│           └── enableNewHeader = false
└── global/
    └── sling:configs/
        └── com.myproject.core.config.SiteConfig/
            ├── analyticsId = "UA-00000"
            ├── socialFacebookUrl = ""
            └── enableNewHeader = false

/content/
├── brand-a/
│   ├── sling:configRef = "/conf/brand-a"     ← links to /conf/brand-a
│   └── en/
│       └── home/
├── brand-b/
│   ├── sling:configRef = "/conf/brand-b"
│   └── en/
```

***

## Defining a Configuration Class

Use Sling CA Config annotations to define a typed configuration:

```java
package com.myproject.core.config;

import org.apache.sling.caconfig.annotation.Configuration;
import org.apache.sling.caconfig.annotation.Property;

@Configuration(
    label = "Site Configuration",
    description = "Site-specific settings like analytics, social, and feature flags"
)
public @interface SiteConfig {

    @Property(label = "Analytics Tracking ID",
              description = "Google Analytics or Adobe Analytics tracking ID")
    String analyticsId() default "";

    @Property(label = "Facebook URL")
    String socialFacebookUrl() default "";

    @Property(label = "Twitter URL")
    String socialTwitterUrl() default "";

    @Property(label = "Enable new header",
              description = "Feature flag for the redesigned header")
    boolean enableNewHeader() default false;

    @Property(label = "Max items per page")
    int maxItemsPerPage() default 10;
}
```

### Supported property types

| Type       | Example          | Notes                        |
| ---------- | ---------------- | ---------------------------- |
| `String`   | `"UA-12345"`     | Default: `""`                |
| `boolean`  | `true` / `false` | Default: `false`             |
| `int`      | `10`             | Default: `0`                 |
| `long`     | `1000L`          | Default: `0L`                |
| `double`   | `3.14`           | Default: `0.0`               |
| `String[]` | `{"a", "b"}`     | Arrays of any primitive type |

***

## Reading CA Config in Sling Models

### Using ConfigurationResolver

```java
import org.apache.sling.caconfig.ConfigurationResolver;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;

import com.myproject.core.config.SiteConfig;

@Model(adaptables = Resource.class)
public class HeaderModel {

    @Self
    private Resource resource;

    @OSGiService
    private ConfigurationResolver configResolver;

    public boolean isNewHeaderEnabled() {
        SiteConfig config = configResolver.get(resource).as(SiteConfig.class);
        // as() may return null when no configuration resource is bound to this context.
        return config != null && config.enableNewHeader();
    }

    public String getAnalyticsId() {
        SiteConfig config = configResolver.get(resource).as(SiteConfig.class);
        return config != null ? config.analyticsId() : null;
    }
}
```

### Adapting from a Resource

`ConfigurationBuilder` can also be obtained by adapting from the current `Resource`, avoiding an explicit `@OSGiService` injection:

```java
import javax.annotation.PostConstruct;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.caconfig.ConfigurationBuilder;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;

import com.myproject.core.config.SiteConfig;

@Model(adaptables = Resource.class)
public class HeaderModel {

    @Self
    private Resource resource;

    private SiteConfig siteConfig;

    @PostConstruct
    protected void init() {
        ConfigurationBuilder builder = resource.adaptTo(ConfigurationBuilder.class);
        siteConfig = builder != null ? builder.as(SiteConfig.class) : null;
    }

    public String getAnalyticsId() {
        return siteConfig != null ? siteConfig.analyticsId() : "";
    }

    public boolean isNewHeaderEnabled() {
        return siteConfig != null && siteConfig.enableNewHeader();
    }
}
```

### Accessing from HTL

Expose the config values through your Sling Model and use them in HTL:

```html
<sly data-sly-use.header="com.myproject.core.models.HeaderModel"/>

<div data-sly-test="${header.newHeaderEnabled}" class="header-v2">
    <!-- New header markup -->
</div>
<div data-sly-test="${!header.newHeaderEnabled}" class="header-v1">
    <!-- Legacy header markup -->
</div>

<!-- Analytics snippet -->
<script data-sly-test="${header.analyticsId}">
    gtag('config', '${header.analyticsId @ context="scriptString"}');
</script>
```

:::tip Always set `@ context` inside `<script>` HTL's auto-context detection usually picks `scriptString` inside `<script>` tags, but set it explicitly to be safe - a value that slips through unescaped can break out of the string literal and execute arbitrary JavaScript. For JSON payloads use `@ context="scriptToken"` or render the value via a `<meta>` tag instead.:::

***

## Nested Configurations

CA Config supports **nested configurations** for complex structures:

```java
@Configuration(label = "Navigation Config")
public @interface NavigationConfig {

    @Property(label = "Max depth")
    int maxDepth() default 3;

    @Property(label = "Navigation items")
    NavigationItem[] items() default {};
}

@Configuration(label = "Navigation Item")
public @interface NavigationItem {

    @Property(label = "Label")
    String label() default "";

    @Property(label = "Path")
    String path() default "";

    @Property(label = "Open in new window")
    boolean newWindow() default false;
}
```

JCR structure for nested configs:

```markdown
/conf/brand-a/sling:configs/
└── com.myproject.core.config.NavigationConfig/
    ├── maxDepth = 3
    └── items/
        ├── item0/
        │   ├── label = "Products"
        │   ├── path = "/content/brand-a/en/products"
        │   └── newWindow = false
        └── item1/
            ├── label = "Support"
            ├── path = "https://support.brand-a.com"
            └── newWindow = true
```

***

## Configuration Collections

For lists of configuration items where each item has the same structure, use **configuration collections**:

```java
@Model(adaptables = Resource.class)
public class SocialLinksModel {

    @Self
    private Resource resource;

    @OSGiService
    private ConfigurationResolver configResolver;

    public Collection<SocialLink> getSocialLinks() {
        return configResolver.get(resource)
            .asCollection(SocialLink.class);
    }
}

@Configuration(label = "Social Link", collection = true)
public @interface SocialLink {

    @Property(label = "Platform")
    String platform() default "";

    @Property(label = "URL")
    String url() default "";

    @Property(label = "Icon")
    String icon() default "";
}
```

***

## OSGi Config vs CA Config

| Aspect          | OSGi Configuration                                  | CA Config                                            |
| --------------- | --------------------------------------------------- | ---------------------------------------------------- |
| **Scope**       | Global or per-run-mode                              | Per-site, per-tenant, per-page                       |
| **Storage**     | `/apps/*/osgiconfig/` (code)                        | `/conf/*/sling:configs/` (content)                   |
| **Managed by**  | Developers (code deployment)                        | Authors or admins (content authoring)                |
| **Typical use** | Service endpoints, feature toggles, system settings | Site-specific branding, analytics IDs, feature flags |
| **Lookup**      | Direct PID lookup                                   | Context-aware tree walk                              |
| **AEMaaCS**     | Deployed via Cloud Manager                          | Stored in content, replicated                        |

**Rule of thumb:** If the value is the same across all sites, use OSGi. If it varies per site or tenant, use CA Config.

***

## wcm.io Context-Aware Configuration Extensions

Most enterprise AEM projects use the [wcm.io CA Config extensions](https://wcm.io/caconfig/), which add:

- **Context path strategies** - more flexible lookup (absolute parent level, root template)
- **Persistence strategies** - storing configs in `/conf` with AEM-compatible structure
- **Editor UI** - a page-level configuration editor for authors
- **Override support** - system-wide overrides via OSGi

### Installation

Add the wcm.io CA Config dependencies to your project:

```xml
<dependency>
    <groupId>io.wcm</groupId>
    <artifactId>io.wcm.caconfig.extensions</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>io.wcm</groupId>
    <artifactId>io.wcm.caconfig.editor</artifactId>
    <scope>provided</scope>
</dependency>
```

### Context path strategy

Configure how CA Config finds the "site root" for a given resource:

```json
{
    "levels": [3],
    "contextPathRegex": "^/content/[^/]+/[^/]+$",
    "configPathPatterns": ["/conf/$1"],
    "templatePaths": ["/conf/myproject/settings/wcm/templates/.*"]
}
```

This tells CA Config: "The site root is always at depth 3 (e.g., `/content/brand/language`), and its config lives under `/conf/brand/`."

***

## Practical Examples

### Analytics tracking per site

```java
@Configuration(label = "Analytics Configuration")
public @interface AnalyticsConfig {

    @Property(label = "Adobe Analytics Report Suite ID")
    String reportSuiteId() default "";

    @Property(label = "Google Tag Manager Container ID")
    String gtmContainerId() default "";

    @Property(label = "Enable tracking")
    boolean enabled() default true;
}
```

### Feature flags per tenant

```java
@Configuration(label = "Feature Flags")
public @interface FeatureFlags {

    @Property(label = "Enable dark mode")
    boolean darkMode() default false;

    @Property(label = "Enable chat widget")
    boolean chatWidget() default false;

    @Property(label = "Enable A/B testing")
    boolean abTesting() default false;

    @Property(label = "Max search results")
    int maxSearchResults() default 20;
}
```

### API keys per environment and site

```java
@Configuration(label = "Integration Settings")
public @interface IntegrationConfig {

    @Property(label = "Maps API Key")
    String mapsApiKey() default "";

    @Property(label = "Search API endpoint")
    String searchEndpoint() default "";

    @Property(label = "Recommendation engine URL")
    String recommendationUrl() default "";
}
```

***

## Best Practices

### Use meaningful configuration class names

The fully qualified class name becomes the JCR node name under `sling:configs`. Keep packages short and names descriptive.

### Provide sensible defaults

Always set default values in the annotation. Code should work without any configuration being authored.

### Keep configurations flat

Avoid deeply nested configurations. Flat structures are easier for authors to manage and for developers to read.

### Version-control initial configurations

Include default `/conf` content in your `ui.content` package so environments start with reasonable defaults.

### Use CA Config for author-manageable values only

Don't put developer-only settings (thread pool sizes, cache TTLs) in CA Config. Those belong in OSGi configurations.

***

## Common Pitfalls

| Pitfall                                | Solution                                                                                                        |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Config values are always defaults      | Check that `sling:configRef` is set on the site root node                                                       |
| `ConfigurationResolver` returns null   | Ensure the CA Config bundles are installed and active; check OSGi console                                       |
| Config works on author but not publish | Replicate the `/conf/` content to publish instances                                                             |
| Nested config items are empty          | Verify the JCR structure matches the annotation hierarchy (child nodes under `items/`)                          |
| wcm.io editor not showing              | Check that the context path strategy is configured correctly for your site structure                            |
| Wrong site config resolved             | Verify the `sling:configRef` points to the correct `/conf/` path; check for overlapping context path strategies |
| Config changes not picked up           | CA Config caching may delay updates; clear the Sling CA Config cache                                            |
