Expand my Community achievements bar.

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

How to Capture Tag Edit, Merge, and Delete Events in AEM?

Avatar

Level 1

Hi Community,

 

I’m currently working on a requirement where I need to trigger a custom service whenever an author edits, merges, or deletes an existing tag in AEM.

 

I explored using ResourceChangeListener, but it appears too generic for this use case. It doesn’t provide specific context—like whether a tag was merged or which properties (e.g., jcr:title or jcr:description) were updated.

 

I'm looking for a way to capture detailed tag-related activities as precisely as possible. Ideally, I want to hook into the tag lifecycle and differentiate between operations such as update vs. merge vs. delete.

 

Has anyone implemented a similar use case or is aware of any specific event handlers or listeners in AEM that are better suited for monitoring tag changes?

 

Any insights or suggestions would be greatly appreciated!

 

Thanks,
Praveen

Topics

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

4 Replies

Avatar

Community Advisor

Hi @PraveenKM1,

Try using javax.jcr.observation.EventListener

This provides more control and granularity than ResourceChangeListener. You can register a listener specifically for events under the /etc/tags path and listen for:

  • PROPERTY_CHANGED – for changes like jcr:title, jcr:description

  • NODE_REMOVED – for tag deletion

  • NODE_ADDED – possibly triggered during merge or creation

Example:

observationManager.addEventListener(
    this,
    Event.PROPERTY_CHANGED | Event.NODE_REMOVED | Event.NODE_ADDED,
    "/etc/tags",
    true,
    null,
    null,
    false
);

Then, in onEvent, inspect the path and event type to determine what changed.

 

Tag merge is tricky because AEM performs it by moving the merged tag’s children and then deleting the merged node.

To detect this:

  • Watch for a sequence of NODE_MOVED or NODE_ADDED events followed by NODE_REMOVED

  • Correlate these in logic (some timestamp or path patterns help)

Unfortunately, there’s no out-of-the-box merge-specific event, so this part may require some custom logic.


Santosh Sai

AEM BlogsLinkedIn


Avatar

Level 4

Using javax.jcr.observation.EventListener with deep filtering on /content/cq:tags

 

 

@component(service = TagChangeListener.class, immediate = true)
public class TagChangeListener implements EventListener {

    @reference
    private ResourceResolverFactory resolverFactory;

    @activate
    protected void activate() throws Exception {
        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "datawrite"))) {
            
            Session session = resolver.adaptTo(Session.class);
            if (session != null) {
                ObservationManager observationManager = session.getWorkspace().getObservationManager();
                observationManager.addEventListener(
                        this,
                        Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_CHANGED | Event.NODE_MOVED,
                        "/content/cq:tags", // or /etc/tags in 6.5
                        true,
                        null,
                        null,
                        false
                );
            }
        }
    }

    @Override
    public void onEvent(EventIterator events) {
        while (events.hasNext()) {
            Event event = events.nextEvent();
            log.info("Tag event type: {}, path: {}", event.getType(), event.getPath());
            // Add custom logic here
        }
    }
}

 

Avatar

Level 1

Hi @SantoshSai / @Nilesh_Mali ,

 

Thanks for your response. I implemented the EventListener as suggested, targeting the /content/cq:tags path. However, the onEvent method is never invoked, regardless of the event types registered in the activate method.

 

I've verified that both the ResourceResolver and Session objects are valid (not null), and the observation listener service is properly registered.

Despite all these checks, editing or deleting tags does not trigger any events. Do you have any suggestions on what might be missing?

 

Thanks,
Praveen

 

 

Avatar

Level 1

Hi Community,

To achieve this requirement, I ended up using a ResourceChangeListener as the EventListener approach didn’t work reliably in my case. I implemented some custom logic by adding a few helper properties (such as prevTitle, prevDescription, and prevBackLinks for merge status) to track changes over time.

 

By comparing the updated values with the previous ones, I’m able to determine what specifically changed. Based on the property that was modified, I adjust the logic accordingly and trigger relevant custom services.

 

Hope this approach is helpful to others facing a similar challenge!

@component(
service = ResourceChangeListener.class,
immediate = true,
property = {
ResourceChangeListener.PATHS + "=/content/cq:tags",
ResourceChangeListener.CHANGES + "=ADDED",
ResourceChangeListener.CHANGES + "=CHANGED",
ResourceChangeListener.CHANGES + "=REMOVED"
})
public class TagResourceChangeListener implements ResourceChangeListener {

@reference private ResourceResolverFactory resourceResolverFactory;

@Override
public void onChange(List < ResourceChange > changes) {

try (ResourceResolver resolver = getResourceResolverUtil(resourceResolverFactory)) {
for (ResourceChange change: changes) {
String path = change.getPath(); // This will be your tag path

switch (change.getType()) {
case ADDED:
handleAddedEvent(path, resolver);
break;
case CHANGED:
handleUpdateOrMergeEvent(path, resolver);
break;
case REMOVED:
handleDeleteEvent(path, resolver);
break;
}
}
} catch (Exception e) {
LOG.error("Error handling resource change", e);
}
}


Thanks,

Praveen