> 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/salesforce/annotations/saving-documents-salesforce.md).

# Saving Annotations Stored in Salesforce

Learn how to save annotations in Salesforce by adding a new ContentVersion record from WebViewer LWC. Generate a base64 string, pass blob data to Apex, and create a new ContentVersion record with this

## Saving Annotations in Salesforce

### Adding a new ContentVersion record from WebViewer LWC

First, you need to generate a base64 string that is sent to the Apex backend. Use the following function in your `config_apex.js` file to achieve this:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
async function saveDocument() {
  const doc = docViewer.getDocument();
  if (!doc) {
    return;
  }
  instance.openElement('loadingModal');

  const fileType = doc.getType();
  const filename = doc.getFilename();
  const xfdfString = await docViewer.getAnnotationManager().exportAnnotations();
  const data = await doc.getFileData({
    // Saves the document with annotations in it
    xfdfString
  });

  let binary = '';
  const bytes = new Uint8Array(data);
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  const base64Data = window.btoa(binary);

  const payload = {
    title: filename.replace(/\.[^/.]+$/, ""),
    filename,
    base64Data,
    contentDocumentId: doc.__contentDocumentId
  }
  // Post message to LWC
  parent.postMessage({ type: 'SAVE_DOCUMENT', payload }, '*');
}
```

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

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
async function saveDocument() {
  const doc = docViewer.getDocument();
  if (!doc) {
    return;
  }
  readerControl.openElement('loadingModal');

  const fileType = doc.getType();
  const filename = doc.getFilename();
  const xfdfString = await docViewer.getAnnotationManager().exportAnnotations();
  const data = await doc.getFileData({
    // Saves the document with annotations in it
    xfdfString
  });

  let binary = '';
  const bytes = new Uint8Array(data);
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  const base64Data = window.btoa(binary);

  const payload = {
    title: filename.replace(/\.[^/.]+$/, ""),
    filename,
    base64Data,
    contentDocumentId: doc.__contentDocumentId
  }
  // Post message to LWC
  parent.postMessage({ type: 'SAVE_DOCUMENT', payload }, '*');
}
```

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

### Passing blob data to Apex

Once you have your document's data converted, you can pass it to Apex. This snippet uses

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
//snipped for brevity
//see full github sample to see how to set up event flow
handleReceiveMessage(event) {
  const me = this;
  if (event.isTrusted && typeof event.data === 'object') {
    switch (event.data.type) {
      case 'SAVE_DOCUMENT':
        saveDocument({ json: JSON.stringify(event.data.payload), recordId: this.recordId }).then((response) => {
          me.iframeWindow.postMessage({ type: 'DOCUMENT_SAVED', response }, '*')
        }).catch(error => {
          console.error(JSON.stringify(error));
        });
        break;
      default:
        break;
    }
  }
}
```

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

### Creating a new ContentVersion record from blob data

Finally, you can use the below Apex code snippet to create a new ContentVersion record. This sample also links the newly created document to the sObject record you started from.

{% tabs %}
{% tab title="Apex" %}
{% code lineNumbers="true" %}

```apex
// force-app\main\default\classes\ContentVersionController.cls
public with sharing class ContentVersionController {
// snipped for brevity
  @AuraEnabled
  public static void saveDocument(String json, String recordId) {
    try {
      PDFTron_ContentVersionPayload pl = PDFTron_ContentVersionPayload.parse(json);
      ContentVersion cv = new ContentVersion();
      cv.ContentLocation = 'S'; //File originated in Salesforce
      if(pl.contentDocumentId != null) {
        cv.ContentDocumentId = pl.contentDocumentId;
        cv.ReasonForChange = 'Saved from WebViewer';//only for file updates
      } else {
        for(ContentDocumentLink cdl : 
          [   SELECT ContentDocumentId, ContentDocument.Title
              FROM ContentDocumentLink 
              WHERE LinkedEntityId = :recordId
              AND ContentDocument.Title = :pl.title ]) {
                
                if(cdl.ContentDocumentId != null) {
                  cv.ContentDocumentId = cdl.ContentDocumentId;
                }
        }
      }
      cv.VersionData = EncodingUtil.base64Decode(pl.base64Data);
      cv.Title = pl.title;
      cv.PathOnClient = pl.filename;
            
      insert cv; 
    } catch (Exception e) {
      throw new AuraHandledException(e.getMessage());
    }
  }
}
```

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

You can also find the full source code for an end to end flow in this [Github repository](https://github.com/ApryseSDK/salesforce-webviewer-attachments/).


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.apryse.com/salesforce/annotations/saving-documents-salesforce.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
