Some test text!

Saving and loading annotations

In this document
chevron_rightUsing files
chevron_rightUsing a database
chevron_rightUsing the document itself

WebViewer is able to import and export PDF annotations with the XFDF format. 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.

In this guide, we will go through relevant APIs and examples for different scenarios.

linkUsing 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,

const xfdfString = annotManager.exportAnnotations();
// <xfdf>
//    <annots>
//      <text subject="Comment" page="0" color="#FFE6A2" ... />
//    </annots>
// </xfdf>
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.

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:

linkUsing a database

Another option is to use a database. You can choose to store the XFDF string for the document and user as described in using files, 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.

To save individual annotations separately, the getAnnotCommand and annotationChanged event are very useful. getAnnotCommand returns XFDF data for all of the annotations that have changed since the last time you called getAnnotCommand.

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

annotManager.on('annotationChanged', function(e) {
  // 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 (e.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.

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 getAnnotCommand 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:

linkUsing the document itself

Another option is to merge the annotations back into the document, avoiding the need to handle XFDF separately. It is achieved by using getFileData 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.

const doc =  docViewer.getDocument();
doc.getFileData({ xfdfString }).then(function(data) {
  var arr = new Uint8Array(data);
  var blob = new Blob([ arr ], { type: 'application/pdf' });
  var 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.

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: