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