> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/annotation/import-export.md).

# Import and Export Annotations with JavaScript

Efficiently manage annotations with our guide on importing/exporting annotations using web. Explore various methods like file, database, and document for seamless data transfer. Master XFDF format for

There are a few ways to import or export annotations such as from a file, a database, or a document. There are also more advanced loading options to help with finer control of the data.

{% tabs %}
{% tab title="Overview" %}

## Importing/exporting annotations

WebViewer is able to import and export PDF annotations with the [XFDF format](/web/annotation/xfdf.md). XFDF is an XML-based standard that is able to represent information about annotations. Saving and loading annotations in WebViewer is the process of saving and loading this XFDF data.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-2bd991f210e766abadcf3b63057457ac0bd855d1%2Fbde90e8fdc00fd7e47dba8bec2559686e755c173-760x477.png?alt=media)

We will go through relevant APIs and examples for different scenarios in the following guides:

[Importing and exporting annotations using a file](#importing-and-exporting-annotations-using-files) To import and export XFDF annotations using a file.

[Importing and exporting annotations using a database](#importing-and-exporting-annotations-using-a-database) To import and export XFDF annotations using a databse.

[Importing and exporting annotations using a document](#importing-and-exporting-annotations-using-the-document) To import and export XFDF annotations using a document.

[Importing and exporting annotation in Salesforce](#importing-and-exporting-annotations-using-salesforce) To import and export XFDF annotations using Salesforce.

[Advanced annotation loading](#advanced-annotation-loading) To perform advanced control of the annotation loading process

## Learn more

[A short introduction to XFDF file format](/web/annotation/xfdf.md) An introduction to XFDF file format
{% endtab %}

{% tab title="Files" %}

## Importing and exporting annotations using files

One of the options is to use XFDF files to save and load annotations. You can use AJAX requests to save and load the XFDF string from the server, and setup the server to write and read XFDF files. For example,

### Import XFDF

Importing annotations require at least the document to be loaded. When using the [`setDocumentXFDFRetriever`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#setDocumentXFDFRetriever) API, the XFDF is imported at the earliest point possible.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer, annotationManager } = instance.Core;

    // Import annotations as soon as we are able to
    documentViewer.setDocumentXFDFRetriever(async () => {
      // load the annotation data
      const response = await fetch('path/to/annotation/server');
      const xfdfString = await response.text();

      // <xfdf>
      //    <annots>
      //      <text subject="Comment" page="0" color="#FFE6A2" ... />
      //    </annots>
      // </xfdf>
      return xfdfString;
    });
  });
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [DocumentViewer.setDocumentXFDFRetriever](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#setDocumentXFDFRetriever)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer, annotManager } = instance;

    // Import annotations as soon as we are able to
    docViewer.setDocumentXFDFRetriever(async () => {
      // load the annotation data
      const response = await fetch('path/to/annotation/server');
      const xfdfString = await response.text();

      // <xfdf>
      //    <annots>
      //      <text subject="Comment" page="0" color="#FFE6A2" ... />
      //    </annots>
      // </xfdf>
      return xfdfString;
    });
  });
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [DocumentViewer.setDocumentXFDFRetriever](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#setDocumentXFDFRetriever)
{% endtab %}
{% endtabs %}

Alternatively, importing annotations can be done with the `importAnnotations` API as well. The earliest point in which XFDF can be imported is `documentLoaded`.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
documentViewer.addEventListener('documentLoaded', async () => {
  const response = await fetch('path/to/annotation/server');
  const xfdfString = await response.text();

  await annotationManager.importAnnotations(xfdfString);
});
```

{% endcode %}

[DocumentViewer#documentLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:documentLoaded__anchor) [AnnotationManager.importAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotations__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
documentViewer.on('documentLoaded', async () => {
  const response = await fetch('path/to/annotation/server');
  const xfdfString = await response.text();

  await annotManager.importAnnotations(xfdfString);
});
```

{% endcode %}

[DocumentViewer#documentLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:documentLoaded__anchor) [AnnotationManager.importAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotations__anchor)
{% endtab %}
{% endtabs %}

### Export XFDF

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer, annotationManager } = instance.Core;

    documentViewer.addEventListener('annotationsLoaded', () => {
      // widgets and links will remain in the document without changing so it isn't necessary to export them
      annotationManager.exportAnnotations({ links: false, widgets: false }).then(xfdfString => {
        fetch('path/to/annotation/server', {
          method: 'POST',
          body: xfdfString // written into an XFDF file in server
        });
        // Full samples are available at the end of this section.
      });
    });
  });
```

{% endcode %}

[DocumentViewer#annotationsLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:annotationsLoaded__anchor) [AnnotationManager.exportAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotations__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer, annotManager } = instance;

    docViewer.addEventListener('annotationsLoaded', () => {
      // widgets and links will remain in the document without changing so it isn't necessary to export them
      annotManager.exportAnnotations({ links: false, widgets: false }).then(xfdfString => {
        fetch('path/to/annotation/server', {
          method: 'POST',
          body: xfdfString // written into an XFDF file in server
        });
        // Full samples are available at the end of this section.
      });
    });
  })
```

{% endcode %}

[DocumentViewer#annotationsLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:annotationsLoaded__anchor) [AnnotationManager.exportAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotations__anchor)
{% endtab %}
{% endtabs %}

## Overview

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-df5b6e1bfc2cb771f35c006e6522a31991d58e56%2F583b19392ad2f0094d39af3fd279ffc399cc532f-597x695.png?alt=media)

In the POST and GET requests you can pass an ID to the server to uniquely identify the XFDF file that should be saved/loaded. You have full flexibility to choose this ID but here are some simple examples:

1. Use the filename as a unique identifier to have one XFDF file per document.
2. Use a combination of the filename and username as a unique identifier to have one XFDF per user per document.

For samples about saving annotations into XFDF files in different backends, see Github repos below:

* [Node.js sample](https://github.com/ApryseSDK/webviewer-annotations-nodejs-sample/)
* [PHP sample](https://github.com/ApryseSDK/webviewer-annotations-php-sample/)
* [ASP.NET (MVC) sample](https://github.com/ApryseSDK/webviewer-annotations-aspnet-sample/)

## documentXFDFRetriever vs importAnnotations

Using the [`documentViewer.setXFDFRetriever`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#setDocumentXFDFRetriever) or `documentXFDFRetriever` WebViewer constructor option is the preferred way of importing annotations from your server if you are adding them all at once initially.

* `documentXFDFRetriever` merges your annotations at the proper time automatically and prevents conflicts and potential flashing of annotations on the page.
* WebViewer will process the internal annotations from the document asynchronously. If you try to import your annotations with `importAnnotations` and your server has modifications to an annotation inside the document, for example on page 10 and WebViewer hasn't loaded the annotations for page 10 yet then there may be a conflict when merging these changes together.
* Using the `annotationsLoaded` event is one way to work around this so that there aren't any conflicts, however this event doesn't fire until annotations on **every** page have been loaded, so for documents with many pages this may take some time. It may also cause annotations to jump if the internal annotation has loaded on a page, then much later your server annotations are imported the annotation may change positions or even be deleted from the document.

`importAnnotations` is fine for importing annotations later, after the document's internal annotations have loaded, however for the initial import of your server annotations `documentXFDFRetriever` is recommended instead.
{% endtab %}

{% tab title="Database" %}

## Importing and exporting annotations using a database

Another option is to use a database to store XFDF. You can choose to store the XFDF string for the document and user as described in [using files](/web/annotation/import-export.md), but with a database you can store and organize the XFDF data so that the XFDF string for each annotation is separate, allowing you to update it individually instead of updating the entire XFDF file for every change. For example, you can store and organize XFDF strings based on a combination of factors like document ID, annotation ID, author, etc.

For example, with a relational database, you can have a table called Annotations which contains annotation ID, document ID and xfdfString columns. In this setup, you can fetch all annotations for a particular document, or just fetch one annotation you are interested in.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-b331d27127442ec1f53324ea02fe1e39c053c4f4%2F8c6560d9f78baf6622580e9e747e3e82427bd3d4-597x695.png?alt=media)

### Export annotation command XFDF

To save individual annotations separately, the [`exportAnnotationCommand`](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotationCommand__anchor) and [`annotationChanged`](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#event:annotationChanged__anchor)[ event](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#event:annotationChanged__anchor) are very useful. `exportAnnotationCommand` returns XFDF data for all of the annotations that have changed since the last time you called `exportAnnotationCommand`.

Annotations that have been added are inside the `add` element, modified annotations are in the `modify` element and deleted annotations have their ID inside the `delete` element of the XFDF. If you call this function on the `annotationChanged` event, you can POST each command to your server to save each annotation change as it happens as opposed to having your user press a save button.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { annotationManager } = instance.Core;

    // Save the annotation XFDF as a transaction command for every change
    annotationManager.addEventListener('annotationChanged', async (annotations, action, { imported }) => {
      // If the event is triggered by importing then it can be ignored
      // This will happen when importing the initial annotations
      // from the server or individual changes from other users
      if (imported) return;

      const xfdfString = await annotationManager.exportAnnotationCommand();
      // <xfdf>
      //   <add>
      //     <text subject="Comment" page="0" color="#FFE6A2" ... />
      //   </add>
      //   <modify />
      //   <delete />
      // </xfdf>
      fetch('path/to/annotation/server', {
        method: 'POST',
        body: xfdfString // written to a database in the server
      });
    });
    // Full samples are available at the end of this section.
  });
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [AnnotationManager.exportAnnotationCommand](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotationCommand__anchor) [AnnotationManager#annotationChanged](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#event:annotationChanged__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { annotManager } = instance;

    // Save the annotation XFDF as transaction command for every change
    annotManager.on('annotationChanged', (annotations, action, { imported }) => {
      // If the event is triggered by importing then it can be ignored
      // This will happen when importing the initial annotations
      // from the server or individual changes from other users
      if (imported) return;

      const xfdfString = annotManager.getAnnotCommand();
      // <xfdf>
      //   <add>
      //     <text subject="Comment" page="0" color="#FFE6A2" ... />
      //   </add>
      //   <modify />
      //   <delete />
      // </xfdf>
      fetch('path/to/annotation/server', {
        method: 'POST',
        body: xfdfString // written to a database in the server
      });
    });
    // Full samples are available at the end of this section.
  })
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [AnnotationManager.getAnnotCommand](https://sdk.apryse.com/api/web/7.3/CoreControls.AnnotationManager.html#getAnnotCommand__anchor) [annotationChanged event](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#event:annotationChanged__anchor)
{% endtab %}
{% endtabs %}

### Import annotation command XFDF

When importing annotations, you can use [`importAnnotationCommand`](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotationCommand__anchor). The earliest point you can call this is during `documentLoaded`.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer, annotationManager } = instance.Core;

    documentViewer.addEventListener('documentLoaded', () => {
      // load the annotation data as a transaction command
      fetch('path/to/annotation/server', {
        method: 'GET'
      }).then(async response => {
        if (response.status === 200) {
          response.text().then(xfdfString => {
            // <xfdf>
            //   <add>
            //     <text subject="Comment" page="0" color="#FFE6A2" ... />
            //   </add>
            //   <modify />
            //   <delete />
            // </xfdf>
            const annotations = await annotationManager.importAnnotationCommand(xfdfString);
            annotations.forEach(a => {
              annotationManager.redrawAnnotation(annotation);
            });
          });
        }
        // Full samples are available at the end of this section.
      });
    });
  });
```

{% endcode %}

[DocumentViewer#documentLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:documentLoaded__anchor) [AnnotationManager.importAnnotationCommand](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotationCommand__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer, annotManager } = instance;

    docViewer.on('documentLoaded', () => {
      // load the annotation data as a transaction command
      fetch('path/to/annotation/server', {
        method: 'GET'
      }).then(response => {
        if (response.status === 200) {
          response.text().then(async xfdfString => {
            // <xfdf>
            //   <add>
            //     <text subject="Comment" page="0" color="#FFE6A2" ... />
            //   </add>
            //   <modify />
            //   <delete />
            // </xfdf>
            const annotations = await annotManager.importAnnotCommand(xfdfString);
            annotations.forEach(a => {
              annotManager.redrawAnnotation(annotation);
            });
          });
        }
        // Full samples are available at the end of this section.
      });
    });
  });
```

{% endcode %}

[DocumentViewer#documentLoaded](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:documentLoaded__anchor) [AnnotationManager.importAnnotCommand](https://sdk.apryse.com/api/web/7.3/CoreControls.AnnotationManager.html#importAnnotCommand__anchor)
{% endtab %}
{% endtabs %}

At this point you'll be saving annotations in realtime, so if you want to have a fully realtime solution you'll need to update annotations on each client in realtime. You can extend realtime saving further by hooking it up to a WebSocket, which can broadcast the new xfdfString in the database to be imported to all the clients. By utilizing `exportAnnotationCommand` instead of `exportAnnotations` you can greatly reduce the size of the data being transferred to/from the annotation server.

For samples about saving annotations into different databases, see links below:

* [SQLite3 sample](https://github.com/ApryseSDK/webviewer-annotations-sqlite3-sample/)
* [SQLite3 + WebSocket sample - realtime](https://github.com/ApryseSDK/webviewer-realtime-collaboration-sqlite3-sample/)
* [Firebase sample - realtime](/web/collaboration/realtime-collaboration.md)
  {% endtab %}

{% tab title="Document" %}

## Importing and exporting annotations using the document

Another option is to merge the annotations back into the document, avoiding the need to handle XFDF separately. It is achieved by using [getFileData](https://sdk.apryse.com/api/web/Core.Document.html#getFileData__anchor) which returns an ArrayBuffer of the PDF with annotations. It can be sent to the server with a POST request, so that the file can be updated on the server.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
  const { documentViewer, annotationManager } = instance.Core;
  const filename = 'myfile.pdf';

    // load a document with annotations
    instance.UI.loadDocument('https://myserver.com/myfile.pdf', { filename });

    // later save the document with updated annotations data
    documentViewer.addEventListener('annotationsLoaded', () => {
      annotationManager.exportAnnotations().then(xfdfString => {
        const doc =  documentViewer.getDocument();
        doc.getFileData({ xfdfString }).then(data => {
          const arr = new Uint8Array(data);
          const blob = new Blob([ arr ], { type: 'application/pdf' });
          const formData = new FormData();
          formData.append('blob', blob);
          fetch(`/server/annotationHandler.js?filename=${filename}`, {
            method: 'POST',
            body: formData // written into a PDF file in the server
          });
        })
      });
    })
  })

// Full sample is available at the end of this section.
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [DocumentViewer.getDocument](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getDocument__anchor) [DocumentViewer.getFiledata](https://sdk.apryse.com/api/web/Core.Document.html#getFileData__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    const annotManager = docViewer.getAnnotationManager();
    const filename = 'myfile.pdf';

    // load a document with annotations
    instance.loadDocument('https://myserver.com/myfile.pdf', { filename });

    // later save the document with updated annotations data
    docViewer.on('annotationsLoaded', () => {
      annotManager.exportAnnotations().then(xfdfString => {
        const doc =  docViewer.getDocument();
        doc.getFileData({ xfdfString }).then(data => {
          const arr = new Uint8Array(data);
          const blob = new Blob([ arr ], { type: 'application/pdf' });
          const formData = new FormData();
          formData.append('blob', blob);
          fetch(`/server/annotationHandler.js?filename=${filename}`, {
            method: 'POST',
            body: formData // written into a PDF file in the server
          });
        })
      })
    })
  })

// Full sample is available at the end of this section.
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [DocumentViewer.getDocument](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getDocument__anchor) [DocumentViewer.getFiledata](https://sdk.apryse.com/api/web/Core.Document.html#getFileData__anchor)
{% endtab %}
{% endtabs %}

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-3f90b26803df93268378b59c08acba8462ed557c%2Fc84014cf56c96b45172dffeb62dbc101ac231f9a-596x462.png?alt=media)

This setup can also be useful for applications that do not have a server or that will handle documents in the user's device locally. However, it would not be suitable for applications where multiple users are annotating and sharing the same document.

For samples about saving annotations into the document itself, see Github repo below:

* [Saving to document sample](https://github.com/ApryseSDK/webviewer-annotations-document-sample/)
  {% endtab %}

{% tab title="Salesforce" %}

## Importing and exporting annotations using Salesforce

Another option for importing and exporting annotation is using Salesforce custom objects to store XFDF data if you are hosting WebViewer in the Salesforce platform. You can create custom object in Salesforce setting page or using `sfdx` command line utility tool. In this example we will create Apex class called `AnnotationController.cls` which will utilize Salesforce SOQL to store and retrieve XFDF data to and from custom objects.

Create custom object in Salesforce setting page, under Manage Objects section. Here is a sample custom object used in this sample

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-dd68484ec6e79ad61186da980585d2d555b8a5b7%2Fd828a9ce8b8b559c2d3562ae6e6087fbccc5dc8d-1626x892.png?alt=media)

Apex class for storing and retrieving annotation XFDF data.

{% code lineNumbers="true" %}

```java
public with sharing class AnnotationController {
    @AuraEnabled
    public static String saveAnnotations(String action, String documentId, String annotationId, String xfdfString) {
        PDFTronAnnotations__c[] annots = [SELECT Id, Content_Version_Id__c, Annotation_Id__c, xfdfString__c
            FROM PDFTronAnnotations__c WHERE Annotation_Id__c = :annotationId];
        Pattern MyPattern = Pattern.compile('<delete>(.*)</delete>');
        Matcher MyMatcher = MyPattern.matcher(xfdfString);
        Boolean isDeleteCommand = MyMatcher.matches();
        if (action == 'delete') {
            // Delete record
            delete annots;
            return 'Success';
        } else if (annots.size() == 0) {
            // Create new record
            PDFTronAnnotations__c newAnnot = new PDFTronAnnotations__c(
                Content_Version_Id__c=documentId,
                Annotation_Id__c=annotationId,
                xfdfString__c=xfdfString
            );
            insert newAnnot;
            return newAnnot.Id;
        } else {
            // Update record
            annots[0].xfdfString__c = xfdfString;
            update annots;
            return annots[0].Id;
        }
    }
    @AuraEnabled
    public static List<Map<String,String>> getAnnotations(String documentId) {
        List<PDFTronAnnotations__c> temp = [SELECT Id, Content_Version_Id__c, Annotation_Id__c, xfdfString__c FROM PDFTronAnnotations__c WHERE Content_Version_Id__c = :documentId];
        List<Map<String,String>> annotations = new List<Map<String,String>>();
        for (Integer i = 0; i < temp.size(); i++) {
            Map<String,String> annot = new Map<String, String>();
            annot.put('xfdfString', temp[i].xfdfString__c);
            annot.put('Annotation_Id', temp[i].Annotation_Id__c);
            annot.put('ContentVersion_Id', temp[i].Content_Version_Id__c);
            annotations.add(annot);
        }
        return annotations;
    }
}
// Full sample is available at the end of this section.
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [DocumentViewer.getDocument](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getDocument__anchor)

Sample app in Lightning Web Component to showcase importing and exporting XFDF stirng to Salesforce custom object.

{% code lineNumbers="true" %}

```js
/** AnnotationController.getAnnotations(id) Apex method */
import getAnnotations from '@salesforce/apex/AnnotationController.getAnnotations';
/** AnnotationController.saveAnnotations(params) Apex method */
import saveAnnotations from '@salesforce/apex/AnnotationController.saveAnnotations';
export default class WvInstance extends LightningElement {
  // Snipped for brevity
  initUI() {
    // ...
    const viewer = new PDFTron.WebViewer({...}, viewerElement);
    viewerElement.addEventListener('ready', () => {
      this.iframeWindow = viewerElement.querySelector('iframe').contentWindow;
    })
  }
  // Snipped for brevity
  handleReceiveMessage(event) {
    if (event.isTrusted && typeof event.data === 'object') {
      switch (event.data.type) {
        case 'LOAD_ANNOTATIONS':
          /**
            * Retrieve/import annotation for the loaded document
            *
            * @param {string} documentId unique id of the loaded document
            * @returns {string} xfdf string
            */
          getAnnotations(event.data.payload)
            .then(result => {
              this.iframeWindow.postMessage(
                { type: 'LOAD_ANNOTATIONS_FINISHED', result },
                window.location.origin
              );
            })
          break;
        case 'SAVE_ANNOTATIONS':
          /**
            * Storing/exporting XFDF string to Salesforce custom object
            *
            * @param {string} action type of action, e.g. delete, update,
            * @param {string} documentId unique id of the document
            * @param {string} annotationId id of the annotation
            * @param {string} xfdfString xfdf string
            */
          saveAnnotations(event.data.payload);
          break;
        default:
          break;
      }
    }
  }
  // Snipped for brevity
}
```

{% endcode %}
{% endtab %}

{% tab title="Advanced loading" %}

## Advanced annotation loading

For more advanced control over the annotation loading process you can use the [`setPagesUpdatedInternalAnnotationsTransform`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#setPagesUpdatedInternalAnnotationsTransform__anchor) function on DocumentViewer. When WebViewer loads a document it will import the annotation data as XFDF and this function allows you to transform that data before it gets loaded into the viewer.

The function that you pass to `setPagesUpdatedInternalAnnotationsTransform` may be called multiple times, specifying the list of pages that the annotation data is a part of.

You can use the function like this:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer } = instance.Core;

    documentViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      // if the pageList is [0, 1, 2, 3] then the data is of all annotations on the first four pages
      // make modifications here
      // ...
      callback(newXfdfData);
    });
  });
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;

    docViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      // if the pageList is [0, 1, 2, 3] then the data is of all annotations on the first four pages
      // make modifications here
      // ...
      callback(newXfdfData);
    });
  });
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Examples

One way to use `setPagesUpdatedInternalAnnotationsTransform` is to replace the original annotation data inside the document with the data from your server. Note that this would also replace any existing links or form fields unless they are also saved on your server.

Alternatively you can modify the existing data that is passed in. When modifying the data it is easiest to first parse it into DOM elements, perform your modifications and then serialize back to a string.

An example of something you might want to do is remove all clickable links:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance.Core;
    docViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      const parser = new DOMParser();
      const xfdfElements = parser.parseFromString(xfdfData, 'text/xml');
      [].forEach.call(xfdfElements.querySelectorAll('link'), e => {
        e.parentNode.removeChild(e);
      });

      const serializer = new XMLSerializer();
      callback(serializer.serializeToString(xfdfElements));
    });
  })
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    docViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      const parser = new DOMParser();
      const xfdfElements = parser.parseFromString(xfdfData, 'text/xml');
      [].forEach.call(xfdfElements.querySelectorAll('link'), e => {
        e.parentNode.removeChild(e);
      });

      const serializer = new XMLSerializer();
      callback(serializer.serializeToString(xfdfElements));
    });
  })
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
{% endtab %}
{% endtabs %}

Here's another example of changing the color attribute of every annotation to blue:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance.Core;
    docViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      const parser = new DOMParser();
      const xfdfElements = parser.parseFromString(xfdfData, 'text/xml');
      const annotations = xfdfElements.querySelector('annots').children;
      [].forEach.call(annotations, annotElement => {
        annotElement.setAttribute('color', '#0000FF');
      });

      const serializer = new XMLSerializer();
      callback(serializer.serializeToString(xfdfElements));
    });
  })
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    docViewer.setPagesUpdatedInternalAnnotationsTransform((xfdfData, pageList, callback) => {
      const parser = new DOMParser();
      const xfdfElements = parser.parseFromString(xfdfData, 'text/xml');
      const annotations = xfdfElements.querySelector('annots').children;
      [].forEach.call(annotations, annotElement => {
        annotElement.setAttribute('color', '#0000FF');
      });

      const serializer = new XMLSerializer();
      callback(serializer.serializeToString(xfdfElements));
    });
  })
```

{% endcode %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
{% endtab %}
{% endtabs %}

With `setPagesUpdatedInternalAnnotationsTransform` you have very fine control over all of the annotation data and can modify it exactly as you like.
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/annotation/import-export.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
