> 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/customizing-salesforce-with-webviewer-and-lwc.md).

# Customize Salesforce with WebViewer and Lightning Web Component (LWC)

Our Salesforce implementation integrates Apryse WebViewer inside a Lightning Web Component (LWC).

The integration allows for bi‑directional communication:

* **LWC to WebViewer** via `postMessage` to the iframe.
* **WebViewer to LWC** via `postMessage` back to the parent window.

This allows Salesforce data and WebViewer actions to stay in sync (e.g., loading documents, updating fields, tracking annotations).

This article highlights entry point of editing when you want to manipulate the data being sent to WebViewer and the information being sent back. This guide also includes how Salesforce and WebViewer communicate.

## Architecture

1. **Lightning Web Component (LWC)**

* Hosts an `<iframe>` that loads WebViewer.
* Uses `postMessage` to send commands and Salesforce payloads into WebViewer.
* Listens for messages coming back from WebViewer.

1. **WebViewer Config File**

* Runs inside the iframe.
* Registers `window.addEventListener('message', ...)` to receive messages from LWC.
* Uses a switch statement to map incoming message types to WebViewer APIs.
* Uses `parent.postMessage(...)` to send information back to the LWC.

## Message Flow

### Example for Load Document Flow

1. **LWC** sends `LOAD_DOCUMENT`.
2. **WebViewer** receives and loads document.
3. **WebViewer** sends back `DOCUMENT_LOADED`.
4. **LWC** receives the `DOCUMENT_LOADED` message.

## LWC to WebViewer, Send Messages

In the LWC, you’ll typically:

1. Reference the iframe.
2. Call `postMessage` on its `contentWindow`.

### Example for Send a Command from LWC

{% tabs %}
{% tab title="HTML" %}
{% code title="apryseWvInstance.js" lineNumbers="true" %}

```html
// Inside your LWC JS controller.
sendCommandToWebViewer() {
  const iframe = this.template.querySelector('iframe');
  if (!iframe || !iframe.contentWindow) {
    return;
  }
  const message = {
    type: 'LOAD_DOCUMENT',
    payload: {
      recordId: this.recordId,
      documentUrl: this.documentUrl,
      // Any other Salesforce data needed by WebViewer.
    }
  };
  iframe.contentWindow.postMessage(message, '*');
}
```

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

Typical fields include:

* **Type:** String that identifies the action (used in WebViewer’s switch).
* **Payload:** Object containing Salesforce-specific data (record IDs, URLs, metadata, etc.).

## WebViewer, Listen for Messages from LWC

In the WebViewer config file (e.g., config\_apex.js), you’ll set up a message listener.

### Basic Listener Structure

{% tabs %}
{% tab title="HTML" %}
{% code title="config\_apex.js" lineNumbers="true" %}

```html
window.addEventListener('message', async (event) => {
  const { type, payload } = event.data || {};
  if (!type) {
    return;
  }
  switch (type) {
    case 'LOAD_DOCUMENT':
      await handleLoadDocument(payload);
      break;
    case 'SET_READONLY':
      await handleSetReadonly(payload);
      break;
    // add additional cases as needed
    default:
      console.warn('Unknown message type received in WebViewer:', type);
      break;
  }
});
```

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

### Example for Call WebViewer APIs

{% tabs %}
{% tab title="HTML" %}
{% code title="config\_apex.js" lineNumbers="true" %}

```html
async function handleLoadDocument(payload) {
  const { documentUrl, recordId } = payload;
  await instance.UI.loadDocument(documentUrl);

  // Optionally, notify LWC that the document has loaded.
  parent.postMessage(
    {
      type: 'DOCUMENT_LOADED',
      payload: { recordId, documentUrl }
    },
    '*'
  );
}
```

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

You can map any message type to WebViewer APIs. For example:

* Load a document.
* Jump to a page.
* Toggle read-only.
* Import/export annotations.
* Trigger save events.

## WebViewer to LWC, Send Messages Back

From within the WebViewer iframe, parent gives you access to the LWC’s window. You can send information back using `parent.postMessage`.

### Common Use Cases

* Notify LWC that:
  * Document finished loading.
  * User created/edited/deleted annotations.
  * User saved or signed a document.
* Send structured payloads (e.g., XFDF, page numbers, flags).

### Example, Send Data to LWC

{% tabs %}
{% tab title="HTML" %}
{% code title="apryseWvInstance.js" lineNumbers="true" %}

```html
function notifyAnnotationsChanged(annotations) {
  // "annotations" could be raw annotation objects or exported XFDF/string.
  parent.postMessage(
    {
      type: 'ANNOTATIONS_UPDATED',
      payload: {
        annotations,
        timestamp: Date.now()
      }
    },
    '*'
  );
}
```

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

You might call this from WebViewer event handlers:

{% tabs %}
{% tab title="HTML" %}
{% code title="config\_apex.js" lineNumbers="true" %}

```html
instance.Core.annotationManager.addEventListener('annotationChanged', async (annotations, action) => {
  notifyAnnotationsChanged({ annotations, action });
});
```

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

## LWC, Receive Messages from WebViewer

On the Salesforce side, you’ll register a message event listener on window in the LWC.

### Example, Handle Messages from WebViewer in LWC

{% tabs %}
{% tab title="HTML" %}
{% code title="apryseWvInstance.js" lineNumbers="true" %}

```html
// LWC JS controller
connectedCallback() {
  window.addEventListener('message', this.handleMessage);
}
disconnectedCallback() {
  window.removeEventListener('message', this.handleMessage);
}
handleMessage(event) {
  // Optional: validate origin here
  // if (event.origin !== this.expectedOrigin) return;
  const { type, payload } = event.data || {};
  if (!type) return;
  switch (type) {
    case 'DOCUMENT_LOADED':
      this.onDocumentLoaded(payload);
      break;
    case 'ANNOTATIONS_UPDATED':
      this.onAnnotationsUpdated(payload);
      break;
    default:
      // Ignore unknown message types
      break;
  }
}
onDocumentLoaded({ recordId, documentUrl }) {
  // e.g., update component state, show toast, etc.
}
onAnnotationsUpdated({ annotations, timestamp }) {
  // e.g., save to Salesforce via Apex, update UI, etc.
}
```

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

## Message Contract Design

To keep things maintainable:

* Use clear type names (e.g., `LOAD_DOCUMENT`, `SAVE_ANNOTATIONS`, `SET_READONLY`).
* Structure payload as a stable contract:
  * Document info (Blob, URL).
  * Salesforce identifiers (recordId, related object Ids).
  * Flags and options (read-only, mode).
* Document all supported message types in your guide, including:
  * Direction (LWC to WebViewer, WebViewer to LWC).
  * Expected payload fields.
  * Behavior triggered.


---

# 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/customizing-salesforce-with-webviewer-and-lwc.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.
