Issue with annotationChanged + exportAnnotationCommand() causing duplicate processing after bulk stamp

WebViewer Version: 11.7.1

Do you have an issue with a specific file(s)? No
Can you reproduce using one of our samples or online demos? No
Are you using the WebViewer server? Yes
Does the issue only happen on certain browsers? No
Is your issue related to a front-end framework? No
Is your issue related to annotations? Yes

Please give a brief summary of your issue:
We are facing an issue while implementing a “Apply Stamp to All Pages” feature using Apryse WebViewer, and we would like your guidance on the correct approach.

Please describe your issue and provide steps to reproduce it:
We are facing an issue while implementing a “Apply Stamp to All Pages” feature using Apryse WebViewer, and we would like your guidance on the correct approach.

Our current implementation

  1. A user places a Rubber Stamp on the current page.
  2. On “Apply to All Pages”, we:
  • Clone the stamp for all other pages using
    annotationManager.getAnnotationCopy()
  • Add the cloned annotations programmatically using
    annotationManager.addAnnotation() / addAnnotations()
  • Export XFDF per annotation using
    annotationManager.exportAnnotations({ annotationList })
  • Save all stamps to the backend in one bulk API call
  1. During bulk stamping, we guard the annotationChanged event using a flag (isBulkStamping) to avoid saving intermediate events.

The problem

After bulk stamping is completed, when the user later adds or modifies any new annotation, we see duplicate processing / saving of previously created stamp annotations.

We traced this behavior to the following logic inside our annotationChanged listener (this part cannot be removed due to existing architecture dependencies):

const xfdfCommands = await annotationManager.exportAnnotationCommand();

for (const annot of annots) {
    if (measurementTools.includes(annot.ToolName)) {
        annot.removeCustomAppearance();
        await annotationManager.redrawAnnotation(annot);
    }
    PartialApryse.ProcessAnnotation(annot, xfdfCommands, action);
}

PartialApryse.SaveDocumentAnnotations();

Key observations:

  • annots only contains the annotations related to the current event
  • exportAnnotationCommand() returns global command history, including commands related to previously added bulk stamps
  • When a new annotation is added later, the global command export appears to include past stamp commands again, leading to duplicate processing/saving on our side

What we would like to understand

  1. Is exportAnnotationCommand() expected to return all pending/global commands, even for annotations that are not part of the current annotationChanged event?
  2. Is there a recommended way to:
  • Scope exportAnnotationCommand() only to the annotations involved in the current event?
  • Or clear/reset command history after a bulk operation?
  1. For bulk annotation workflows, is Apryse’s recommended pattern:
  • To rely only on exportAnnotations({ annotationList }) for delta-based saving?
  • Or is there a supported way to safely combine annotationChanged with exportAnnotationCommand() without reprocessing old annotations?
  1. Are there any best practices or flags we should use when annotations are added programmatically (bulk add) to avoid them being reprocessed on subsequent user actions?

We want to ensure we are following the correct and supported Apryse workflow, especially for bulk annotation scenarios, without introducing duplicate saves or unintended side effects.

Code for adding stamp to All Pages:

 if (selectedOption === 'all') {
     const bulkRequest = [];
     isBulkStamping = true;
     for (let pageNumber = 1; pageNumber <= totalPages; pageNumber++) {
         if (pageNumber === currentPage) {
             const currentStampXfdf = await annotationManager.exportAnnotations({
                 annotationList: [lastStamp],
                 action: 'add'
             });
             bulkRequest.push({
                 caseId: $('#Id').val(),
                 contentItemId: $('#ContentItemId').text().trim(),
                 annotData: currentStampXfdf,
                 annotType: lastStamp.ToolName,
                 annotId: lastStamp.Id,
                 pageNumber: pageNumber,
                 currentUserId: userID,
                 status: 'None',
             });

             continue;
         };

         const clone = annotationManager.getAnnotationCopy(lastStamp);
         clone.PageNumber = pageNumber;
         annotationManager.addAnnotation(clone);
         annotationManager.redrawAnnotation(clone);
         const xfdf = await annotationManager.exportAnnotations({
             annotationList: [clone],
             action: 'add'
         });
         bulkRequest.push({
             caseId: $('#Id').val(),
             contentItemId: $('#ContentItemId').text().trim(),
             annotData: xfdf,
             annotType: clone.ToolName,
             annotId: clone.Id,
             pageNumber: pageNumber,
             currentUserId: userID,
             status: 'None',
         });
     }
     PartialApryse.ApplyBulkStamp(bulkRequest);
     isBulkStamping = false;
 }

Please provide a link to a minimal sample where the issue is reproducible:

1 Like

Hello, I’m AiRon, an automated tech support bot :robot:
While you wait for one of our customer support representatives, I can help you with your question.


It looks like you’re dealing with issues related to duplicate processing of annotations when using annotationChanged and exportAnnotationCommand() in a bulk annotation workflow. This can occur due to the global nature of exportAnnotationCommand(), which returns commands for all changes since it was last called.

Some things you can try:

  • Understand that exportAnnotationCommand() is designed to return all pending XFDF commands globally since its last invocation. This is expected behavior.

  • To avoid reprocessing, consider resetting the command history after your bulk operation by calling exportAnnotationCommand() immediately after processing the bulk stamps. This will clear out the commands related to the bulk operation.

  • For bulk annotation workflows, you can rely on exportAnnotations({ annotationList }) to save specific annotations without including global changes.

  • Consider using a flag to prevent the annotationChanged event from triggering your save logic during programmatic annotation additions. You’ve already implemented isBulkStamping, which is a good practice.

  • Invoke exportAnnotationCommand() after bulk operations to clean the command history, ensuring subsequent calls only include new changes.




Here are some resources that might help you:



Hopefully, this helps you solve your problem while you wait for a human to get back to you.

1 Like

Hello @rishumishra2001,

Thank you for posting on our forum.

Firstly, exportAnnotationCommand will return all operations that occurred since the last time that API was called (think anytime an annotationChanged event would be triggered). In other words, it is not bound to the current event you are calling it from. See our API doc for exportAnnotationChanged here for more information: https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotationCommand

Secondly, we currently do not have any API which manages what events are tracked into the exportAnnotationCommand call. That said, since exportAnnotationCommand only tracks operations since the last call to the API, you can also call the API without saving the result to essentially “clear” the history it was tracking.

As for recommended workflows, this largely depends on the use case behind exporting/importing annotations. Typically for workflows similar to an “auto-save”, importAnnotationCommand/exportAnnotationCommand would typically be the recommended approach. On the other hand, if your aim is to simply export and import a specific set of annotations, while also having more granular control over what is exported/imported, we would recommend importAnnotations/exportAnnotations as you can pass in a list of options into exportAnnotations to control what is exported. You can find our guide here which goes into more detail of comparing the two options: https://docs.apryse.com/web/guides/annotation/import-export

In terms of best practices, we do not have any particular recommendations for bulk adding annotations, but if you have any questions regarding the documentation provided, or have any more specific questions you’d like to raise regarding the usage of certain APIs, don’t hesitate to reach out and we can investigate further!

Best Regards,
Jacob Romano Carlsen
Web Development Support Engineer
Apryse Software Inc.

1 Like