Expand my Community achievements bar.

Submissions are now open for the 2026 Adobe Experience Maker Awards.
SOLVED

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

Avatar

Level 2

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

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Accepted Solution

Avatar

Correct answer by
Level 4

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);

    @reference
    private ResourceResolverFactory resolverFactory;

    @reference
    private LiveRelationshipManager liveRelationshipManager;

    private static final String LANGUAGE_MASTER_PATH = "/content/language-master";

    @Override
    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.

View solution in original post

3 Replies

Avatar

Community Advisor

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

AEM BlogsLinkedIn


Avatar

Level 10

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;
    }
}

Avatar

Correct answer by
Level 4

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);

    @reference
    private ResourceResolverFactory resolverFactory;

    @reference
    private LiveRelationshipManager liveRelationshipManager;

    private static final String LANGUAGE_MASTER_PATH = "/content/language-master";

    @Override
    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.