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.
<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:
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, '*');
}
}
}
'*') 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.
- 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.