> 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/open-save/opening-documents-salesforce.md).

# Opening a document from the Salesforce Database

Learn how to manage documents in Salesforce with ContentVersion object and Apex code snippets. Implement document versioning and viewing with WebViewer. Full code sample available on Github. Salesforc

If you have a document uploaded into your instance of Salesforce, it will have a record in the database in the [`ContentVersion`](https://developer.salesforce.com/docs/atlas.en-us.sfFieldRef.meta/sfFieldRef/salesforce_field_reference_ContentVersion.htm) table. The `ContentVersion` object allows for versioning of the `ContentDocument` which is linked to a Salesforce record via the `ContentDocumentLink`.

{% tabs %}
{% tab title="Small Files" %}
For Salesforce files smaller than 25 MB, you can use this method to retrieve and display them in WebViewer.

### Process Flow

![small file process flow](https://306473577-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfmJo9lQOEYOFBgOyFp26%2Fuploads%2Fgit-blob-d0b599c0f37145cf669097b2dd256d1c106843f0%2F3216b6d3b5033be71e8b1202c68b00010f8e76bf-2963x1408.png?alt=media)

### ContentVersionController.cls

{% code lineNumbers="true" %}

```apex
// force-app\main\default\classes\Apryse_ContentVersionController.cls

//snipped for brevity
@AuraEnabled(Cacheable=true) 
public static ContentVersionWrapper getFileDataFromId(String Id) {
    try {
        Long max = 1024 * 1024 * 25; // 25 MB
        ContentVersion cv = [SELECT Title, FileExtension, ContentSize, VersionData, ContentDocumentId, LastModifiedDate FROM ContentVersion WHERE Id = :Id];
        if(cv.ContentSize > max) {
            throw new ApryseException('Your file size must not exceed ' + convertBytesToFormattedString(max) + ' - current file size: ' + convertBytesToFormattedString(cv.ContentSize));
        }
        return new ContentVersionWrapper(cv);
    } catch(Exception e) {
        throw new AuraHandledException(e.getMessage());
    }
}

//sample wrapper class
public class ContentVersionWrapper {
  @AuraEnabled
  public String name { get; set; }
  
  @AuraEnabled
  public String body { get; set; }
  
  @AuraEnabled
  public ContentVersion cv { get; set; }
  
  public ContentVersionWrapper(ContentVersion contentVer) {
    this.cv = contentVer;
    this.name = contentVer.Title + '.' + contentVer.FileExtension;
    this.body = EncodingUtil.base64Encode(contentVer.VersionData);
  }
}
```

{% endcode %}

### ApryseWvInstance.js

{% code lineNumbers="true" %}

```html
// force-app/main/default/lwc/apryseWvInstance/ApryseWvInstance.js

//snipped for brevity
import { registerListener, unregisterAllListeners } from 'c/pubsub';

connectedCallback() {
  registerListener('blobSelected', this.handleBlobSelected, this);
  //register other listeners here
}

disconnectedCallback() {
  //unregister all listeners here
}

handleBlobSelected(record) {
  let blobby = new Blob([_base64ToArrayBuffer(record.body)], {
    type: mimeTypes[record.cv.FileExtension]
  });

  const payload = {
    blob: blobby,
    extension: record.cv.FileExtension,
    filename: record.name,
    documentId: record.cv.Id
  };
  
  this.iframeWindow.postMessage({type: 'OPEN_DOCUMENT_BLOB', payload} , '*');
}
```

{% endcode %}

### config\_apex.js

{% code lineNumbers="true" %}

```js
//snipped for brevity
window.addEventListener("message", receiveMessage, false);

function receiveMessage(event) {
  if (event.isTrusted && typeof event.data === 'object') {
    switch (event.data.type) {
      case 'OPEN_DOCUMENT_BLOB':
        const { blob, extension, filename, documentId } = event.data.payload;
        instance.loadDocument(blob, { extension, filename, documentId })
        break;
      default:
        break;
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Large Files" %}
When working with Salesforce files that are 25 MB or larger, attempting to load the file’s `VersionData` will trigger an Apex memory error. To avoid this issue, you have two options:

1. Store the files externally in a system like Amazon S3
2. Use a proxy server to retrieve large files directly from Salesforce

In this guide, we’ll focus on the proxy server option.

### Process Flow

![large files process flow](https://306473577-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfmJo9lQOEYOFBgOyFp26%2Fuploads%2Fgit-blob-06f27cfe3e114b2376e393787dfd9a87b0c364a0%2F259c66d7d244cbd116e8102ef8fc6a4ddb6a34a2-3782x2064.png?alt=media)

### ContentVersionController.cls

{% code lineNumbers="true" %}

```apex
//snipped for brevity
@AuraEnabled(Cacheable=true) 
public static ContentVersionWrapper getFileDataFromId(String Id) {
  try {
    Long max = 1024 * 1024 * 15; // 25 MB
    ContentVersion cv = [SELECT Title, FileExtension, ContentSize, ContentDocumentId, LastModifiedDate FROM ContentVersion WHERE Id = :Id];
    ContentVersionWrapper cvw = new ContentVersionWrapper(cv);

    if(cv.ContentSize < max) {
      //Only query VersionData if size < 25 MB
      ContentVersion cvData = [SELECT VersionData FROM ContentVersion WHERE Id = :Id];
      cvw.body = EncodingUtil.base64Encode(cvData.VersionData);
    }else{
      //Set proxy url with Id
      cvw.url = 'https://{proxy_url}/document/'+ Id;
    }

    return cvw;
  } catch(Exception e) {
      throw new AuraHandledException(e.getMessage());
  }
}

//sample wrapper class
public class ContentVersionWrapper {
  @AuraEnabled
  public String name {get; set;}
  
  @AuraEnabled
  public String body { get; set; }
  
  @AuraEnabled
  public String url { get; set; }

  @AuraEnabled
  public ContentVersion cv { get; set; }

  public ContentVersionWrapper(ContentVersion contentVer) {
    this.cv = contentVer;
    this.name = contentVer.Title + '.' + contentVer.FileExtension;
  }
}
```

{% endcode %}

### ApryseWvInstance.js

{% code lineNumbers="true" %}

```html
// force-app/main/default/lwc/apryseWvInstance/ApryseWvInstance.js

//snipped for brevity
import { registerListener, unregisterAllListeners } from 'c/pubsub';

connectedCallback() {
  registerListener('blobSelected', this.handleBlobSelected, this);
  //register other listeners here
}

disconnectedCallback() {
  //unregister all listeners here
}

handleBlobSelected(record) {

  const payload = {
    blobOrUrl: null,
    extension: record.cv.FileExtension,
    filename: record.name,
    documentId: record.cv.Id
  };

  // If the record has a URL, use the proxy server
  if(record.url){
    payload.blobOrUrl = record.url;
  }else{
  // Otherwise, convert the base64 body to a Blob
    payload.blobOrUrl = new Blob([_base64ToArrayBuffer(record.body)], {
      type: mimeTypes[record.cv.FileExtension]
    });
  }

  this.iframeWindow.postMessage({type: 'OPEN_DOCUMENT_BLOB', payload} , '*');
}
```

{% endcode %}

### config\_apex.js

{% code lineNumbers="true" %}

```js
//snipped for brevity
window.addEventListener("message", receiveMessage, false);

function receiveMessage(event) {
  if (event.isTrusted && typeof event.data === 'object') {
    switch (event.data.type) {
      case 'OPEN_DOCUMENT_BLOB':
        const { blobOrUrl, extension, filename, documentId } = event.data.payload;
        instance.loadDocument(blobOrUrl, { extension, filename, documentId })
        break;
      default:
        break;
    }
  }
}
```

{% endcode %}

### Proxy

This is a sample proxy server using express.

{% code lineNumbers="true" %}

```js
//snipped for brevity
const conn = new jsforce.Connection({ loginUrl: process.env.SF_LOGIN_URL });

app.get('/document/:docId', async (req, res) => {
    const docId = req.params.docId;

    
    // 1) Find the latest ContentVersion for the ContentDocument
    const [ver] = await conn
      .sobject("ContentVersion")
      .find(
        { Id: docId, IsLatest: true },
        ["Id", "Title", "FileExtension"]
      )
      .limit(1);


    const filename = `${ver.Title || ver.Id}.${ver.FileExtension || "bin"}`;
    const contentType = resolveResponseType(req, filename);

    // 2) Set headers and stream the blob from Salesforce to the client
    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.setHeader("Content-Type", contentType);

    const sfStream = conn
      .sobject("ContentVersion")
      .record(ver.Id)
      .blob("VersionData");

    // Mirror SF Content-Length if provided
    sfStream.on("response", (sfRes) => {
      const len = sfRes.headers["content-length"];
      if (len) res.setHeader("Content-Length", len);
    });

    sfStream.on("error", (e) => {
      console.error("Salesforce stream error:", e);
      if (!res.headersSent) res.status(502).send("Error streaming file from Salesforce");
      else res.end();
    });

    sfStream.pipe(res);
})
```

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

### Converting a base64 string to blob inside your WebViewer LWC

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

```js
function _base64ToArrayBuffer(base64) {
  var binary_string =  window.atob(base64);
  var len = binary_string.length;
  var bytes = new Uint8Array( len );
  for (var i = 0; i < len; i++)        {
      bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}
```

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

## Code sample

You can find a full code sample that implements opening documents in Salesforce on our [Github repository](https://github.com/ApryseSDK/webviewer-samples/tree/main/webviewer-salesforce-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/open-save/opening-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.
