In a previous discussion on AEM MSM: Multilingual & Multichannel with Custom Rollout, the foundation for creating tailored rollout actions was established. However, when large, multi-channel websites are managed, deep repository complexities are often encountered.
Specifically, the way rollout configurations are inherited, linked, broken, or permanently blocked by page deletions must be fully understood. In this continuation, the hidden Java Content Repository (JCR) structures are broken down in simple terms, and a clear, programmatic solution is provided for a notorious built-in limitation: the cq:excludedPaths trap.
How Rollout Configurations Are Stored in a Live Copy Node
When a Live Copy page is created in Adobe Experience Manager (AEM), the relationship with its blueprint source is tracked directly within the repository. To store this relationship, a hidden configuration node named cq:LiveSyncConfig is automatically generated beneath the page’s jcr:content node.
The structure of this tracking configuration is organized as follows:
[cq:Page] (Your Live Copy Page)
└── [nt:unstructured] jcr:content (Contains Live Sync mixins)
└── [cq:LiveSyncConfig] cq:LiveSyncConfig
├── cq:master (String) -> /content/blueprint-source-path
├── cq:rolloutConfigs (String[]) -> [/libs/msm/wcm/rolloutconfigs/default]
└── cq:isDeep (Boolean) -> true
The multi-value property cq:rolloutConfigs contains the exact list of triggers and rules that dictate how content updates are pushed down. By default, this setup is inherited seamlessly from parent folders down the content tree.
Managing Live Copy Lifecycle Relationships
To control how content flows from a blueprint to a Live Copy, several relationship states and actions are provided by the AEM Multi-Site Manager interface.
Link and Unlink Relationship
Link: When a Live Copy is first created, a live connection is established between the blueprint source and the target page. Updates made to the source can be rolled out automatically or manually.
Unlink: If a page needs to become entirely independent from its source permanently, the relationship can be unlinked. Consequently, the cq:LiveSyncConfig node is completely removed from the JCR, converting the page into a standard, standalone AEM page.
Synchronize and Reset Relationship
Synchronize: This action is performed to manually push data changes from the master blueprint down to the linked Live Copy page.
Reset: If localized changes have accidentally corrupted a Live Copy page, a reset operation can be triggered. By executing a reset, any locally modified components are completely overwritten, and the page state is reverted to match the exact blueprint source.
Broken and Suspend Relationships
Suspend (Broken Relationship): When a temporary local deviation is required, a relationship can be suspended. This action pauses the rollout sync without permanently deleting the relationship properties. Once a relationship is marked as suspended, a status flag is appended to the node, allowing updates to be safely ignored until the relationship is resumed.
Overriding Hierarchical Rollout Configurations on Child Pages
In complex web deployments, a single rollout strategy is rarely sufficient for an entire site structure. For instance, while a parent folder might be configured to sync automatically, a specific child page may need to be updated only manually to allow for localized reviews.
To achieve this flexibility, the built-in inheritance must be broken at the child level. When settings are customized on a child page via the AEM user interface, a brand new, independent cq:LiveSyncConfig node is written directly to that child’s properties. Consequently, the parent folder’s rollout rules are overridden, and the child page begins following its own unique synchronization path.
/content/mysite/en (Blueprint Source Root)
└── /content/mysite/en/products (Child Page)
│
[MSM Rollout Triggered]
│
v
/content/mysite/fr (Live Copy Root) ──> [cq:LiveSyncConfig] (Inherited Parent Rules)
└── /content/mysite/fr/products ──> [cq:LiveSyncConfig] (Inheritance Broken / Overridden)
The cq:excludedPaths Trap: The Deleted Page Issue
A severe operational bottleneck occurs when a page inside a Live Copy is deleted by an author without first being detached from its blueprint source. When this action is completed, AEM attempts to prevent future updates from breaking by silently appending the deleted page’s path to a hidden blocklist property named cq:excludedPaths. This property is saved on the nearest active parent cq:LiveSyncConfig node.
Why This is a Problem:
Permanent Block: Because the deleted path remains permanently registered in the hidden cq:excludedPaths array, that specific branch is completely ignored by future rollouts.
Cannot Recreate: If an author goes back to the master blueprint page and attempts to roll out or recreate that deleted page with the same name again, the page will not be recreated.
No Out-of-the-Box Solution: Currently, no button, tool, or dashboard is provided by Adobe to clear paths from this hidden metadata blocklist.
Resolution: Programmatic JCR Cleanup via Custom Java Service
Since native product controls do not exist to remediate this issue, the blocklist must be modified directly at the JCR node level. To accomplish this, a custom OSGi service can be built to safely find the cq:excludedPaths property and purge the blocked path.
The sample code below illustrates how this cleanup can be handled programmatically:
package in.stacknowledge.core.services;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Service utility executed to systematically remove blocked paths from the cq:excludedPaths array.
*/
public class LiveCopyRecoveryService {
private static final Logger log = LoggerFactory.getLogger(LiveCopyRecoveryService.class);
private static final String LIVE_SYNC_CONFIG_PATH = "/jcr:content/cq:LiveSyncConfig";
private static final String EXCLUDED_PATHS_PROP = "cq:excludedPaths";
public void removePathFromExclusions(ResourceResolver resolver, String liveCopyRootPath, String pathToInclude) {
// The hidden Live Sync configuration path is resolved
String configPath = liveCopyRootPath + LIVE_SYNC_CONFIG_PATH;
Resource configResource = resolver.getResource(configPath);
if (configResource == null) {
log.error("The LiveSyncConfig node could not be found at path: {}", configPath);
return;
}
// The properties are opened in modifiable mode so edits can be applied
ModifiableValueMap valueMap = configResource.adaptTo(ModifiableValueMap.class);
if (valueMap != null && valueMap.containsKey(EXCLUDED_PATHS_PROP)) {
String[] currentExclusions = valueMap.get(EXCLUDED_PATHS_PROP, String[].class);
if (currentExclusions != null) {
List<String> updatedList = new ArrayList<>(Arrays.asList(currentExclusions));
// If the target path is identified within the blocklist, it is removed
if (updatedList.remove(pathToInclude)) {
try {
if (updatedList.isEmpty()) {
// The entire property is deleted if no other blocked paths remain
valueMap.remove(EXCLUDED_PATHS_PROP);
} else {
// The updated, cleaned array is saved back to the node
valueMap.put(EXCLUDED_PATHS_PROP, updatedList.toArray(new String[0]));
}
// All changes are committed directly to the Oak repository database
resolver.commit();
log.info("Path {} was successfully removed from the blocklist at {}", pathToInclude, configPath);
} catch (PersistenceException e) {
log.error("A JCR error was encountered while updating the node", e);
}
}
}
}
}
}
By utilizing this programmatic approach, the hidden blocklist metadata is successfully cleared. Once the path is removed, standard MSM rollout functionality is restored, allowing deleted pages to be perfectly recreated and synchronized across all multi-channel environments.
The explanation of how `cq:LiveSyncConfig` stores Live Copy relationships makes it much easier to understand what’s happening behind the scenes in AEM, especially when troubleshooting rollout behavior. I’m interested to see how your approach handles the `cq:excludedPaths` limitation in real projects, since that’s an issue that often becomes noticeable only after content structures grow more complex.