Delete all MSM live copy in other language / country at once | Community
Skip to main content
Level 2
June 18, 2025
Solved

Delete all MSM live copy in other language / country at once

  • June 18, 2025
  • 2 replies
  • 510 views

Hi community,

 

I am managing a site that has a multi language / country following the msm structure.

When I need to delete a page in the language master folder, is there a way that I can apply the delete action to the sibling live copy pages under the other country / language folder? 

 

Site Structure

  • language-master
    • english
      • target-page-to-be-deleted
  • us
    • roll-out-page-to-be-deleted
  • other-country
  • ...

 

 

Thanks,

Chris

Best answer by ButhpurKiran

implement an "event-listener" that listens for page deletions in the source language-master folder, finds live copies, and deletes them automatically.

 

Create a Sling event listener that listens to JCR NODE_REMOVED events under /content/language-master.

package com.example.aem.listeners; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.Session; import org.apache.sling.api.resource.PersistenceException; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import javax.jcr.RepositoryException; import javax.jcr.Node; import com.day.cq.wcm.msm.api.LiveRelationshipManager; import com.day.cq.wcm.msm.api.LiveRelationship; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.HashMap; import java.util.Map; @Component( immediate = true, service = EventListener.class, property = { "event.topics=org/apache/jackrabbit/jcr/observation/namespace/REMOVED", "event.types=NODE_REMOVED" } ) public class PageDeleteListener implements EventListener { private static final Logger LOG = LoggerFactory.getLogger(PageDeleteListener.class); @3214626 private ResourceResolverFactory resolverFactory; @3214626 private LiveRelationshipManager liveRelationshipManager; private static final String LANGUAGE_MASTER_PATH = "/content/language-master"; @9944223 public void onEvent(EventIterator events) { while (events.hasNext()) { Event event = events.nextEvent(); try { String path = event.getPath(); // Check if deleted node is under language-master if (path != null && path.startsWith(LANGUAGE_MASTER_PATH)) { LOG.info("Detected deletion of source page: {}", path); // Obtain a ResourceResolver with write permission Map<String, Object> params = new HashMap<>(); params.put(ResourceResolverFactory.SUBSERVICE, "datawrite"); // make sure this service user exists with required perms try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(params)) { Resource deletedPageResource = resolver.getResource(path); // Since node is deleted, this resource might be null // Instead, get the parent path to find live copies String sourcePagePath = path; // Get live relationships for the source path Collection<LiveRelationship> liveRelationships = liveRelationshipManager.getLiveRelationships(sourcePagePath); for (LiveRelationship liveRel : liveRelationships) { Resource liveCopy = liveRel.getLiveCopy(); if (liveCopy != null) { LOG.info("Deleting live copy page at: {}", liveCopy.getPath()); deletePage(resolver, liveCopy); } } resolver.commit(); } catch (Exception e) { LOG.error("Failed to delete live copies for source page: {}", path, e); } } } catch (RepositoryException e) { LOG.error("Error processing deletion event", e); } } } private void deletePage(ResourceResolver resolver, Resource pageResource) throws PersistenceException { if (pageResource != null) { resolver.delete(pageResource); LOG.info("Deleted page {}", pageResource.getPath()); } } }

 

- Configure a service user (e.g., datawrite)  with write permissions on your content paths (including language-master and live copies).

- Bind the service user in your OSGi config for ResourceResolverFactory under Apache Sling Service User Mapper Service.

- Make sure the service user has jcr:removeChildNodes and jcr:removeNode permissions on the live copy paths.

- Deploy your listener bundle.

- Test by deleting a page under /content/language-master and verify if the corresponding live copies get deleted.

2 replies

SantoshSai
Community Advisor
Community Advisor
June 18, 2025

Hi @chrisch4,

In AEM's MSM (Multi Site Manager), deleting a page in the language master does not automatically delete the corresponding live copies in the country/language sites. This is by design to avoid accidental data loss across localized sites that may have customizations.

However, there are a few options to propagate deletion manually or semi-automatically:

Option 1: Use "Manage Publication" to Deactivate First

Before deletion, deactivate/unpublish the master page and its live copies:

  1. Go to the language-master page (e.g., /language-master/english/target-page-to-be-deleted).

  2. Use Manage Publication to deactivate the page and select all live copies to include.

  3. Once deactivated, delete the page in the language master.

  4. Then, manually delete each corresponding live copy in the country/language folders.

Option 2: Manual Deletion via References Panel
  1. Select the language master page in Sites.

  2. Open the References panel.

  3. Under "Live Copies", you’ll see all live copies for that page.

  4. You can click each one and delete them manually.

Option 3: Automate with Script (Advanced)

If you frequently need to delete master pages and their live copies:

  • Write a custom Groovy script or AEM Workflow using JCR APIs:

    • Identify the page in the language master.

    • Traverse live copy references using the cq:LiveRelationship or blueprint info.

    • Delete each live copy programmatically.

This method requires AEM developer access and proper testing in non-prod first.

Santosh Sai
giuseppebaglio
Level 10
June 18, 2025

If you need to proceed with the automated approach, it's not too complicated. You can create a workflow that performs the following actions:

 

- Find live copies of target page

- First deactivate and delete them

- Finally, deactivate and delete the target page

 

Here is a code sample to retrieve live copy pages:

import com.day.cq.wcm.msm.api.LiveRelationshipManager; import com.day.cq.wcm.msm.api.LiveRelationship; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; @Reference private LiveRelationshipManager liveRelationshipManager; public List<Resource> getAllLiveCopies(ResourceResolver resourceResolver, String sourcePath) { List<Resource> liveCopies = new ArrayList<>(); try { Resource sourceResource = resourceResolver.getResource(sourcePath); if (sourceResource != null) { // Get all live relationships where this page is the source Iterator<LiveRelationship> relationships = liveRelationshipManager.getLiveRelationships(sourceResource, null, null); while (relationships.hasNext()) { LiveRelationship relationship = relationships.next(); Resource liveCopyResource = relationship.getTargetResource(); if (liveCopyResource != null) { liveCopies.add(liveCopyResource); } } } } catch (Exception e) { // Handle exception log.error("Error retrieving live copies for path: " + sourcePath, e); } return liveCopies; }

Code to deactivate pages:

import com.day.cq.replication.Replicator; import com.day.cq.replication.ReplicationActionType; import com.day.cq.replication.ReplicationException; import org.apache.sling.api.resource.ResourceResolver; import javax.jcr.Session; @Reference private Replicator replicator; public boolean deactivatePage(ResourceResolver resourceResolver, String pagePath) { try { Session session = resourceResolver.adaptTo(Session.class); // Deactivate the page replicator.replicate(session, ReplicationActionType.DEACTIVATE, pagePath); return true; } catch (ReplicationException e) { log.error("Error deactivating page: " + pagePath, e); return false; } }
ButhpurKiran
ButhpurKiranAccepted solution
Level 4
June 18, 2025

implement an "event-listener" that listens for page deletions in the source language-master folder, finds live copies, and deletes them automatically.

 

Create a Sling event listener that listens to JCR NODE_REMOVED events under /content/language-master.

package com.example.aem.listeners; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.Session; import org.apache.sling.api.resource.PersistenceException; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import javax.jcr.RepositoryException; import javax.jcr.Node; import com.day.cq.wcm.msm.api.LiveRelationshipManager; import com.day.cq.wcm.msm.api.LiveRelationship; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.HashMap; import java.util.Map; @Component( immediate = true, service = EventListener.class, property = { "event.topics=org/apache/jackrabbit/jcr/observation/namespace/REMOVED", "event.types=NODE_REMOVED" } ) public class PageDeleteListener implements EventListener { private static final Logger LOG = LoggerFactory.getLogger(PageDeleteListener.class); @3214626 private ResourceResolverFactory resolverFactory; @3214626 private LiveRelationshipManager liveRelationshipManager; private static final String LANGUAGE_MASTER_PATH = "/content/language-master"; @9944223 public void onEvent(EventIterator events) { while (events.hasNext()) { Event event = events.nextEvent(); try { String path = event.getPath(); // Check if deleted node is under language-master if (path != null && path.startsWith(LANGUAGE_MASTER_PATH)) { LOG.info("Detected deletion of source page: {}", path); // Obtain a ResourceResolver with write permission Map<String, Object> params = new HashMap<>(); params.put(ResourceResolverFactory.SUBSERVICE, "datawrite"); // make sure this service user exists with required perms try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(params)) { Resource deletedPageResource = resolver.getResource(path); // Since node is deleted, this resource might be null // Instead, get the parent path to find live copies String sourcePagePath = path; // Get live relationships for the source path Collection<LiveRelationship> liveRelationships = liveRelationshipManager.getLiveRelationships(sourcePagePath); for (LiveRelationship liveRel : liveRelationships) { Resource liveCopy = liveRel.getLiveCopy(); if (liveCopy != null) { LOG.info("Deleting live copy page at: {}", liveCopy.getPath()); deletePage(resolver, liveCopy); } } resolver.commit(); } catch (Exception e) { LOG.error("Failed to delete live copies for source page: {}", path, e); } } } catch (RepositoryException e) { LOG.error("Error processing deletion event", e); } } } private void deletePage(ResourceResolver resolver, Resource pageResource) throws PersistenceException { if (pageResource != null) { resolver.delete(pageResource); LOG.info("Deleted page {}", pageResource.getPath()); } } }

 

- Configure a service user (e.g., datawrite)  with write permissions on your content paths (including language-master and live copies).

- Bind the service user in your OSGi config for ResourceResolverFactory under Apache Sling Service User Mapper Service.

- Make sure the service user has jcr:removeChildNodes and jcr:removeNode permissions on the live copy paths.

- Deploy your listener bundle.

- Test by deleting a page under /content/language-master and verify if the corresponding live copies get deleted.