Skip to main content

Title: How to Embed Visualforce in LWC with Two-Way Communication

While Lightning Web Components (LWC) represent modern Salesforce frontend development, legacy application features, complex PDF generation engines, or third-party libraries often still rely on standard Visualforce pages. Fortunately, you can embed legacy Visualforce pages inside an LWC iframe and establish smooth, bidirectional communication using standard Web APIs.

In plain words: You can embed a Visualforce page inside an LWC using an <iframe> element and share real-time data back and forth using the browser's native JavaScript window.postMessage() method.

Prerequisites

  • Fundamental understanding of Lightning Web Components and Visualforce page structures.
  • Familiarity with basic JavaScript event listeners (addEventListener).
  • Access to a Salesforce Developer org or Sandbox instance.

Step 1: Setting Up the Visualforce Page

Create a standard Visualforce page (SampleVFPage.page) equipped with JavaScript listeners to accept messages from the parent container and reply back:

<apex:page showHeader="false" sidebar="false">
    <div style="padding: 15px; font-family: Arial, sans-serif;">
        <h1>Visualforce Frame</h1>
        <p id="lwcMessage">Waiting for message from LWC...</p>
        <button onclick="sendToLWC()">Send Message to LWC</button>
    </div>

    <script>
        // Listen for messages coming from parent LWC
        window.addEventListener('message', function(event) {
            // Verify domain origin for security if needed
            if (event.data && event.data.type === 'FROM_LWC') {
                document.getElementById('lwcMessage').innerText = 'Received: ' + event.data.payload;
            }
        });

        // Send a message back to parent LWC
        function sendToLWC() {
            const message = {
                type: 'FROM_VF',
                payload: 'Hello from Visualforce!'
            };
            // Send payload to parent window frame
            window.parent.postMessage(message, '*');
        }
    </script>
</apex:page>

Step 2: Creating the Lightning Web Component Markup

In your LWC template, render a standard iframe targeting your Visualforce page URL path (/apex/SampleVFPage):

<!-- embeddedVFPage.html -->
<template>
    <lightning-card title="LWC Container" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            <lightning-button 
                label="Send Data to VF Page" 
                variant="brand" 
                onclick={sendMessageToVF}>
            </lightning-button>

            <p class="slds-m-top_small">
                Received Message: <strong>{vfMessage}</strong>
            </p>

            <div class="vfContainer slds-m-top_medium" style="height: 250px; border: 1px solid #ccc;">
                <iframe 
                    src="/apex/SampleVFPage" 
                    width="100%" 
                    height="100%" 
                    style="border:none;">
                </iframe>
            </div>
        </div>
    </lightning-card>
</template>

Step 3: Implementing Two-Way Communication in JavaScript

Attach global message listeners during component lifecycle hooks (connectedCallback / disconnectedCallback) to receive child messages, and target the iframe window to send outbound payloads:

PostMessage Strategy: Call iframe.contentWindow.postMessage() to post messages into the iframe, and listen for inbound responses via window-level message events.
// embeddedVFPage.js
import { LightningElement, track } from 'lwc';

export default class EmbeddedVFPage extends LightningElement {
    @track vfMessage = 'No messages received yet.';

    connectedCallback() {
        // Bind the window message event handler
        window.addEventListener('message', this.handleVFMessage.bind(this));
    }

    disconnectedCallback() {
        // Clean up event listeners to prevent memory leaks
        window.removeEventListener('message', this.handleVFMessage.bind(this));
    }

    handleVFMessage(event) {
        if (event.data && event.data.type === 'FROM_VF') {
            this.vfMessage = event.data.payload;
        }
    }

    sendMessageToVF() {
        const iframe = this.template.querySelector('iframe');
        if (iframe && iframe.contentWindow) {
            const messagePayload = {
                type: 'FROM_LWC',
                payload: 'Hello from LWC at ' + new Date().toLocaleTimeString()
            };
            // Send payload to child frame
            iframe.contentWindow.postMessage(messagePayload, '*');
        }
    }
}
Security Trap: Using wildcard target origins ('*') in postMessage() broadcasts messages across any domain rendering inside or outside your iframe. In production environments, replace '*' with your specific Salesforce domain URL or validate event.origin inside event listeners to guard against cross-site scripting vulnerabilities.
Key Summary: LWC & VF Two-Way Messaging
  • Outbound (LWC to VF): Select the iframe element and call iframeElement.contentWindow.postMessage(payload, targetOrigin).
  • Inbound (VF to LWC): Call window.parent.postMessage(payload, targetOrigin) from inside Visualforce scripts.
  • Event Capture: Handle communications on both ends using window.addEventListener('message', callback).
  • Lifecycle Management: Always unbind listeners in disconnectedCallback() to avoid lingering runtime listeners.

Conclusion

Embedding a Visualforce page within a Lightning Web Component allows you to retain pre-existing Visualforce functional assets while upgrading your primary user interface to modern Lightning standards. By connecting them via postMessage, you establish seamless inter-component communication while preserving flexibility across Salesforce frameworks.