{"id":32996,"date":"2026-01-30T10:41:20","date_gmt":"2026-01-30T09:41:20","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=32996"},"modified":"2026-05-07T13:19:43","modified_gmt":"2026-05-07T11:19:43","slug":"aem-sites-configuration-with-sling-context-aware-configuration","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/aem-sites-configuration-with-sling-context-aware-configuration\/","title":{"rendered":"AEM sites configuration with Sling Context-Aware Configuration"},"content":{"rendered":"\n<p>When developing any nontrivial AEM project, sooner or later, we run into the topic of storing and accessing configuration data. This could be various information, from platform-related data, API keys, to component configuration, and others.<\/p>\n\n\n\n<p>This data in projects is usually used one or a mix of below:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run Mode,<\/li>\n\n\n\n<li>OSGi configuration,<\/li>\n\n\n\n<li>Page properties,<\/li>\n\n\n\n<li>Custom configuration pages,<\/li>\n\n\n\n<li>Sling Context-Aware Configuration.<\/li>\n<\/ul>\n\n\n\n<p>We must keep in mind that each of those was created for a specific purpose and should not, as a best practice, be used for other things.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Run modes<\/strong> \u2013 this should be used for environment-related data, such as author or publish configuration.<\/li>\n\n\n\n<li><strong>OSGi configuration<\/strong> \u2013 best used for non-authorable, system-level configurations such as schedulers, services, caching, and integrations.<\/li>\n\n\n\n<li><strong>Page properties<\/strong> \u2013 a good way of configuring behavior on a single page level, but hard to scale, hard to govern, and technical information should not be stored here.<\/li>\n\n\n\n<li><strong>Custom Configuration Pages<\/strong> \u2013 usually a custom solution, flexible but requires development and maintenance.<\/li>\n\n\n\n<li><strong>Sling Context-Aware Configuration<\/strong> \u2013 we will look into it deeper in this article.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Sling Context\u2011Aware Configuration<\/strong><\/h2>\n\n\n\n<p>As per the documentation, Context-Aware Configurations (CAC) are configurations related to a content resource or a resource tree, e.g., a website or a tenant site. Additionally, it supports both:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>inheritance \u2013 so you do not have to setup value for each page of a subtree,<\/li>\n\n\n\n<li>fallback \u2013 values are searched for up to the configuration root.<\/li>\n<\/ul>\n\n\n\n<p>Default implementation stores data under \/conf while providing additional fallbacks. The configuration is referenced by the subtree root via the sling:ConfigRef property that points to a configuration node. All nodes under that root will try to find configuration nodes there, falling back to a higher level if necessary.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n\/content\n   \/tenant1\n      @sling:configRef=&quot;\/conf\/tenant1&quot;\n      \/region1\n         @sling:configRef=&quot;\/conf\/tenant1\/region1&quot;\n\t     \/site1\n         @sling:configRef=&quot;\/conf\/tenant1\/region1\/site1&quot;\n         \n\/conf\n   \/tenant1\n      \/sling:configs\n         \/x.y.z.MyConfig\n            @prop1 = &#039;tenant1&quot;\n      \/region1\n         \/sling:configs\n            \/x.y.z.MyConfig\n               @prop1 = &#039;region1&quot;\n\t     \/site1\n            \/sling:configs\n               \/x.y.z.MyConfig\n                 @prop1 = &#039;site1&#039;\n<\/pre><\/div>\n\n\n<p>The configuration data is stored under a bucket node sling:configs, which in turn stores configuration nodes whose names are derived from an annotation class \u2013 <code>x.y.z.MyConfig<\/code> that stores the actual property values \u2013 prop1, etc.<\/p>\n\n\n\n<p>As we will see in the next part of the article, it is highly customizable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><strong>Configuration Interfaces<\/strong><\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n@Configuration(label=&quot;Configuration&quot;, description=&quot;Sample configuration&quot;)\npublic @interface MyConfig {\n\n    @Property(label=&quot;Parameter #1&quot;, description=&quot;Describe me&quot;)\n    String param1();\n}\n<\/pre><\/div>\n\n\n<p>Those are regular interfaces annotated with @Configuration and @Property. It is possible to define both nested configuration interfaces and properties that are lists.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n@Configuration(label=&quot;Configuration List&quot;, description=&quot;Sample list configuration&quot;)\npublic @interface MyConfigList {\n\n    @Property(label=&quot;Key Value Pairs&quot;, description=&quot;List of key value pairs&quot;)\n    KeyValue&#x5B;] keyValues();\n\t\n\t@interface KeyValue {\n\t\t@Property\n\t\tString key();\n\t\t\n\t\t@Property\n\t\tString valye\n\t}\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>The configuration data may then be read in the following way<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\nResource contentResource = resourceResolver.getResource(&quot;\/content\/tenant1\/region1\/site1\/page1&quot;);\nMyConfig config = contentResource.adaptTo(ConfigurationBuilder.class).as(MyConfig.class);\n<\/pre><\/div>\n\n\n<p>As we can see, this is very convenient as all the logic for providing correct context, finding property values, inheritance, and fallbacks is handled by the Context-Aware Configuration library without the need to write any custom code. It is worth to mention that Sling CAC is part of the OOTB AEM installation.<\/p>\n\n\n\n<p><strong>But what if we have a special requirement, but still would like to utilize CAC?<\/strong> Fortunately, as mentioned above, it may be tailored to our needs.<\/p>\n\n\n<div class=\"nsw-o-blogersii-banner\">\n            <picture>\n            <source srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2026\/04\/Blog-AEC-Desktop_.jpg\" media=\"(min-width: 992px)\" >\n            <source srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2026\/04\/Blog-AEC-Mob_.jpg\" media=\"(min-width: 300px)\" >            <img decoding=\"async\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2026\/04\/Blog-AEC-Desktop_.jpg\" alt=\"\"  class=\"\"  >\n        <\/picture>\n        <div class=\"cnt\">\n                    <div class=\"nsw-m-title-block -h3 -invert  -has-title-margin-bottom-0 -has-title-font-weight-bold\">\n                                <h2 class=\"nsw-m-title-block__title\">Sii x Adobe Experience Cloud<\/h2>\n                <\/div>\n                            <p class=\"has-nsw-p-4-font-size has-invert-color\">\n                As an official Adobe Experience Cloud partner, we implement content management, customer journey orchestration, and marketing automation solutions to streamline operations and increase campaign performance.\n            <\/p>\n                            <a  href=\"https:\/\/sii.pl\/en\/what-we-offer\/enterprise-platforms\/adobe\/\" class=\"nsw-a-button -ghost -banner-button\"   >\n        <span>Adobe offering<\/span>\n    <\/a>\n            <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Customization and adjusting to project needs<\/strong><\/strong><\/h2>\n\n\n\n<p>A possible drawback of storing configuration data in CAC is that there is no out-of-the-box, user-friendly UI to edit the configuration. The only way is to either provide it as part of content packages or via CRXDE.<\/p>\n\n\n\n<p><strong>But what if we want authors to be able to edit at least part of the configuration and publish it?<\/strong> Fortunately, this can be done with some custom code for editing and storing the data, still utilizing all of the CAC benefits when reading it.<\/p>\n\n\n\n<p>Let&#8217;s take a look at a scenario where we would like to merge storing data on regular pages as regular components, but still read the values in a standardized way.<\/p>\n\n\n\n<p>Assuming we would have a site structure like:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n\/content\n   \/tenant1\n      \/region1\n\t     \/site1\n\t        \/configuration \u2013 configuration page\n\t           \/jcr:content\n\t              \/myconfig \u2013 configuration component\n\t                 @prop1 = \u201cvalue\u201d\n<\/pre><\/div>\n\n\n<p>where there is a special configuration page with instances of components holding properties we look for, first, we need to implement our own ContextPathStrategy.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n@Component(\n   service = ContextPathStrategy.class\n   property = {\n      \u201cservice.ranking.integer:1000\u201d   \n   }\n}\npublic class CustomContextPathStrategy implments ContextPathStrategy {\n   private static final String CONFIG_PAGE = &quot;configuration&quot;;\n   \n   @Override\n   public Iterator&lt;ContextResource&gt; findContextResource(Resource resource) {\n      List&lt;ContextResource&gt; contextResources = new ArrayList();\n      \n      Resource current = resource; \n      while (current != null) {\n         if (current.getPath().startsWith(&quot;\/content\/&quot;)) { \n            Resource configurationPage = current.getChild(CONFIG_PAGE); \n            if(configurationPage != null) {\n               contextResources.add(new ContextResource(configurationPage, current.getPath()));\n            }\n         }\n         current.getParent();\n      }\n      return contextResources.iterator();\n   }\n}\n<\/pre><\/div>\n\n\n<p>This OSGI service will be used when looking for the context path. High ranking ensures it will be used first. In findContextResource, we are looking up the resource hierarchy up to \/content for a resource with a \/configuration child \u2013 our configuration page. If found, it is added to the result.<\/p>\n\n\n\n<p>Next, it&#8217;s time to use this service in a custom implementation of ConfigurationResourceResolvingStrategy. Here we are explicitly injecting a CustomContextPathStrategy that will return valid context paths. We are then iterating through those context paths, looking for a component whose name is derived from the configuration interface (MyConfig) and following the rules of how AEM is creating component names (getComponentName method). If found, the method returns it; if not, it falls back to the next context path, following the same scenario.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n@Component {\n   serice = ConfigurationResourceResolvingStrategy.class,\n   property = {\n      &quot;serivce.ranking:Integer=1000&quot;\n   }\n}\npublic class CustomConfigurationResourceResolvingStrategy implements ConfigurationResourceResolvingStrategy {\n   @Reference(target=&quot;(component.name=x.y.z.CustomContextPathStrategy)&quot;)\n   private ContextPathStrategy contextPathStrategy;\n   \n   @Override\n   public Resource getResource(Resource resourceResolver, Collection&lt;String&gt; bucketName) {\n      String className = configName.substring(configName.lastIndex(&#039;.&#039;) + 1);\n      return findContextResources(contentResource)\n         .map(configurationPage -&gt; findChildResource(configurationPage, getComponentName(className)))\n         .filter(Object::nonNull)\n         .findFirst()\n         .orElse(null)\n   }\n   \n   private String getComponentName(String className) {\n      String componentName = className.toLowerCase();\n      if(componentName.length() &gt; 20) {\n            return componentName.substring(0, 20);\n      }\n      return componentName;\n   }\n   \n   private Resource findChildResource(Resource resource, String name) {\n      if(resource.getName().equals(name)) {\n         return resource;\n      }\n      for(Resource child : resource.getChildren()) {\n         Resource result = findChildResource(child, name);\n         if(resoult != null) {\n            return result;\n         }\n      }\n      return null;\n   }\n\n   ...\n   implmenetation of the rest of the methods\n   ...\n   \n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Summary<\/strong><\/strong><\/h2>\n\n\n\n<p>In a similar way, collection-type properties can be handled; we just need to remember that the config name looks like <code>x.y.z.MyConfig\/jcr:content\/keyValues<\/code>, and this needs to be both stored and read properly. The \/jcr:content part comes from an internal Granite implementation, and I was not able to change that during my experiments.<\/p>\n\n\n\n<p>Those two services are enough to implement our custom scenario and are a great example of Sling Context-Aware Customization flexibility.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Further considerations<\/strong><\/strong><\/h2>\n\n\n\n<p>There is a custom third part library that provides UI and some custom configuration strategies: <a href=\"https:\/\/wcm.io\/caconfig\/\" target=\"_blank\" rel=\"noopener\" title=\"\" rel=\"nofollow\" >Context-Aware Configuration | wcm.io<\/a><\/p>\n\n\n\n<p>It&#8217;s always good to take a look at the official docs as well: <a href=\"https:\/\/sling.apache.org\/documentation\/bundles\/context-aware-configuration\/context-aware-configuration.html\" target=\"_blank\" rel=\"noopener\" title=\"\" rel=\"nofollow\" >Apache Sling :: Apache Sling Context-Aware Configuration<\/a><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;32996&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;1&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;2&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5&quot;,&quot;size&quot;:&quot;30&quot;,&quot;title&quot;:&quot;AEM sites configuration with Sling Context-Aware Configuration&quot;,&quot;width&quot;:&quot;159&quot;,&quot;_legend&quot;:&quot;{score}\\\/5&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 159px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 2px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 30px; height: 30px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 24px;\">\n            5\/5    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>When developing any nontrivial AEM project, sooner or later, we run into the topic of storing and accessing configuration data. &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/aem-sites-configuration-with-sling-context-aware-configuration\/\">Continued<\/a><\/p>\n","protected":false},"author":768,"featured_media":32994,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1320],"tags":[6011,1526,1353],"class_list":["post-32996","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-aem-2-en","tag-guidebook","tag-e-commerce-en"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2026\/01\/Coding.jpg","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/32996"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/768"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=32996"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/32996\/revisions"}],"predecessor-version":[{"id":33800,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/32996\/revisions\/33800"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/32994"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=32996"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=32996"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=32996"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}