> 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/get-started/salesforce-faq.md).

# WebViewer in Salesforce - Frequently Asked Questions

Discover how to use WebViewer in Salesforce for advanced document editing and viewing. Learn how to optimize WebViewer for Salesforce, overcome limitations, and load the WebViewer entry point with loa

## What is WebViewer?

WebViewer is a pure client-side JavaScript library to annotate, view, and edit documents such as PDF, MS Office, Images, CAD, Video and more.

## How can we use WebViewer in Salesforce?

We can embed WebViewer UI in Salesforce, contained in an iframe. WebViewer source code can be stored in Salesforce static resources, which allows for advanced document working client side without external dependencies and without your data ever leaving your Salesforce organization.

## How do I optimize WebViewer for Salesforce

In Salesforce, we need to upload our WebViewer source files to static resources. Salesforce imposes a file size limit of 5MB for static resources, so we need to [optimize our WebViewer worker files](https://www.youtube.com/watch?v=GfZr_LvwmNg).

Since static resources are served from a different origin, we need to use a `config.js` file which runs in context of the WebViewer iframe. You can check out a [sample ](https://github.com/ApryseSDK/salesforce-webviewer-attachments/blob/main/force-app/main/default/staticresources/myfiles/config_apex.js)[`config.js`](https://github.com/ApryseSDK/salesforce-webviewer-attachments/blob/main/force-app/main/default/staticresources/myfiles/config_apex.js)[ file](https://github.com/ApryseSDK/salesforce-webviewer-attachments/blob/main/force-app/main/default/staticresources/myfiles/config_apex.js).

## What are the limitations of WebViewer in Salesforce?

Due to Salesforce's [Governor Limits](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm), we can only use Salesforce files within the allowed heap size (6MB/12MB async). WebViewer supports external file storage, from providers such as AWS, GCP, Azure, Dropbox and others - this allows for file support for sizes up to 2GB.

## How to load WebViewerEntry Point with loadScript()?

After uploading your WebViewer worker files, make sure you use Salesforce's [loadScript API](https://developer.salesforce.com/docs/component-library/bundle/lightning-platform-resource-loader/documentation/) to load the WebViewer entry point like so:

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

```js
//in your pdftronInstance.js LWC file
renderedCallback() {
  //your renderedCallback logic here...

  var self = this;
  if (this.uiInitialized) {
    return; // prevent multiple initializations of WebViewer on each re-render
  }
  this.uiInitialized = true;

  Promise.all([
    loadScript(self, libUrl + '/webviewer.min.js'),
  ])
  .then(() => this.initUI())
  .catch(console.error);
}
```

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

## How to instantiate WebViewer with Salesforce using a config file?

In Salesforce, the config file is used to set worker paths for our optimized WebViewer files and to access WebViewer objects and APIs. You can check out [WebViewer fundamentals](/web/get-started/guides/learn-more-overview.md), which explains how WebViewer creates the UI app inside an iframe, so that [Core namespaces and classes](/web/ui-customization/core.md#main-namespaces-and-classes) are encapsulated.

The iframe window and document object are still accessible through [contentWindow](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentWindow/) and [contentDocument](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentDocument/), but it can still be tricky to run scripts or listen to events happening inside the iframe.

The config file gives you easy access to the [Document](https://sdk.apryse.com/api/web/Core.Document.html), [DocumentViewer](https://sdk.apryse.com/api/web/Core.DocumentViewer.html) and [AnnotationManager](https://sdk.apryse.com/api/web/Core.AnnotationManager.html) objects (among others) which can allow you to make more complicated customizations.

To instantiate WebViewer with a config file you just need to set the `config` option in the WebViewer constructor. For example:

<pre class="language-js" data-line-numbers><code class="lang-js">//in your pdftronInstance.js LWC file
const viewerElement = this.template.querySelector('div')

const viewer = new WebViewer({
  // snipped for brevity

  config: myfilesUrl + this.config, // path to the config.js file in your static resources
  // l: '<code class="expression">visitor.claims.wvKey || "YOUR_LICENSE_KEY"</code>_HERE',
}, viewerElement);
</code></pre>

## How do I setup worker paths in the config.js?

Before using WebViewer, you need to specify the worker paths for WebViewer with the below API calls. Keep in mind that some workers, such as office workers are only required if you want to include MS Office support and can be safely omitted if not required for your implementation.

{% hint style="info" %}
As of 11.4, the worker paths have changed to continue Salesforce compatibility with size limitations on deployed resources.
{% endhint %}

{% tabs %}
{% tab title="11.4+ SDK" %}
{% code lineNumbers="true" %}

```js
// staticresource/config.js

window.instance.UI.setFontPath('../../../..' + resourceURL + 'font_assets/fonts');

// office workers
window.Core.setOfficeWorkerPath(resourceURL + 'office')
window.Core.setOfficeAsmPath(resourceURL + 'office_asm');
window.Core.setOfficeResourcePath(resourceURL + 'office_resource');

//office editing
window.Core.setOfficeEditorWorkerPath(resourceURL + 'office_edit');

window.Core.ContentEdit.setWorkerPath(resourceURL + 'content_edit');
window.Core.ContentEdit.setResourcePath(resourceURL + 'content_edit_resource');

// pdf workers
window.Core.setPDFResourcePath(resourceURL + 'resource')
if (custom.fullAPI) {
  window.Core.setPDFWorkerChunkPaths([
    resourceURL + 'full_worker_0',
    resourceURL + 'full_worker_1',
  ])
  window.Core.setPDFWorkerPath(resourceURL + 'pdf_full');
} else {
  window.Core.setPDFWorkerChunkPaths([
    resourceURL + 'lean_worker_0',
    resourceURL + 'lean_worker_1',
  ])
  window.Core.setPDFWorkerPath(resourceURL + 'pdf_lean')
}

// external 3rd party libraries
window.Core.setExternalPath(resourceURL + 'external')
```

{% endcode %}
{% endtab %}

{% tab title="8.0 SDK" %}
{% code lineNumbers="true" %}

```js
// staticresource/config.js

// office workers - optional
window.Core.setOfficeWorkerPath(resourceURL + 'office')
window.Core.setOfficeAsmPath(resourceURL + 'office_asm');
window.Core.setOfficeResourcePath(resourceURL + 'office_resource');

// legacy office workers - optional
window.Core.setLegacyOfficeWorkerPath(resourceURL + 'office')
window.Core.setLegacyOfficeAsmPath(resourceURL + 'office_asm');
window.Core.setLegacyOfficeResourcePath(resourceURL + 'office_resource');

// pdf workers
window.Core.setPDFResourcePath(resourceURL + 'resource')

if (custom.fullAPI) {
  window.Core.setPDFWorkerPath(resourceURL + 'pdf_full')
  window.Core.setPDFAsmPath(resourceURL + 'asm_full');
} else {
  window.Core.setPDFWorkerPath(resourceURL + 'pdf_lean')
  window.Core.setPDFAsmPath(resourceURL + 'asm_lean');
}

// external 3rd party libraries
window.Core.setExternalPath(resourceURL + 'external');
```

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

## How do I pass custom data to WebViewer?

You can use a custom object and pass it to your WebViewer constructor to pass data from LWC to your config file upon initialization. To do this you can use the `custom` option in the WebViewer constructor. The property expects a string value. So for example to pass an object:

<pre class="language-js" data-line-numbers><code class="lang-js">// LWC js file
var myObj = {
    libUrl: libUrl, // staticresources/lib.zip folder used for setting workerPaths
    myfilesUrl: myfilesUrl,
    fullAPI: this.fullAPI || false,
    namespacePrefix: '', // pass a namespace prefix for managedpackages
};
    //var url = myfilesUrl + '/WVSF-Account-info-sample.pdf';

const viewerElement = this.template.querySelector('div')
    // eslint-disable-next-line no-unused-vars
const viewer = new WebViewer({
// snipped for brevity
    custom: JSON.stringify(myObj),
    // l: '<code class="expression">visitor.claims.wvKey || "YOUR_LICENSE_KEY"</code>_HERE',
    }, viewerElement);
</code></pre>

Then inside the config file you access this data as follows:

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

```js
instance.UI.addEventListener('viewerLoaded', () => {
  const custom = JSON.parse(instance.UI.getCustomData());
  console.log(custom.fullAPI); // outputs true or false
});

instance.UI.addEventListener('documentLoaded, () => {
  const docViewer = instance.Core.documentViewer;
  docViewer.setCurrentPage(custom.startPage);
});
```

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

## How do I use a config file when the path is on another domain

If you have the WebViewer path on a different domain than your app, then to protect against XSS attacks you will need to edit the `lib/ui/configorigin.txt` file to whitelist your app's domain(s). Add each domain on a separate line, for example:

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

```sh
http://localhost:3000
https://example.com
```

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

Domains in this list will be allowed to have WebViewer specify config files that can be loaded.

## Accessing outer page inside the iframe

If you want to access the outer page from inside the iframe (for Salesforce it is the parent LWC component), for example from code in your config file, you can access the parent window using `window.parent`. So if you defined an API that's loaded on your HTML page, you could access it from inside the iframe like `window.parent.myApi.myFunction()`.

You could hook into WebViewer events and notify the parent LWC like so:

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

```js
//config.js
window.addEventListener('viewerLoaded', () => {
  // notify parent LWC that viewer has loaded and is ready for processing documents
  window.parent.postMessage({ type: 'VIEWER_LOADED' }, window.origin);
}
```

{% endcode %}
{% 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/salesforce/get-started/salesforce-faq.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.
