@api methods or by publishing events across the platform using Lightning Message Service (LMS).
During digital transformation projects, organizations rarely replace entire Visualforce applications overnight. Instead, developers embed modern Lightning Web Components inside existing Visualforce pages. To make these hybrid pages interactive, your Visualforce JavaScript must be able to invoke functions and pass data directly into the hosted LWC.
1. Architectural Approaches: Lightning Out vs. LMS
Salesforce provides two architectural patterns for calling LWC logic from Visualforce:
- Pattern 1: Lightning Out with Public
@apiMethods (Direct Embedding): The Visualforce page embeds the LWC using<apex:includeLightning />and an Aura Dependency App. Once instantiated, Visualforce client-side JavaScript calls methods exposed on the LWC via the@apidecorator. - Pattern 2: Lightning Message Service (LMS) (Decoupled Messaging): Visualforce publishes a message to a
LightningMessageChannelusingsforce.one.publish(), and the embedded LWC listens to the channel and executes its internal methods automatically upon receiving the payload.
- Bridge Mechanism:
<apex:includeLightning />+$Lightning.use(). - Dependency Container: Aura Application (
<aura:application extends="ltng:outApp">). - Exposing Methods:
@api methodName()in the LWC JavaScript class. - Messaging Alternative:
sforce.one.publish(channel, payload)via LMS.
2. Step-by-Step Implementation via Lightning Out
Below is a working implementation showing a Visualforce page invoking an @api method on an embedded LWC component.
@api Method (actionHandlerLwc.js)Expose the target method publicly using the
@api decorator.
import { LightningElement, api, track } from 'lwc';
export default class ActionHandlerLwc extends LightningElement {
@track message = 'Waiting for Visualforce action...';
@track actionCount = 0;
// Public method callable by external containers (Visualforce / Aura)
@api
performAction(customText) {
this.actionCount += 1;
this.message = `Action executed successfully! Message: "${customText}" (Total Runs: ${this.actionCount})`;
console.log('LWC method executed from external caller:', customText);
}
}
actionHandlerLwc.html)
<template>
<div class="slds-box slds-theme_default slds-m-around_medium">
<h3 class="slds-text-heading_small slds-m-bottom_small">Embedded LWC Target</h3>
<p class="slds-text-color_success"><strong>{message}</strong></p>
</div>
</template>
LwcBridgeApp.app)Declares the component dependencies required by Lightning Out.
<aura:application access="GLOBAL" extends="ltng:outApp">
<aura:dependency resource="c:actionHandlerLwc"/>
</aura:application>
InvokeLwcDemo.page)Loads the component via
$Lightning.createComponent, stores the DOM reference, and calls the method on button click.
<apex:page showHeader="true" sidebar="false">
<apex:includeLightning />
<div style="padding: 20px; font-family: sans-serif;">
<h2>Visualforce Container Page</h2>
<p>Click the button below to invoke the JavaScript method inside the embedded LWC:</p>
<input type="text" id="inputPayload" value="Hello from Visualforce!" style="padding: 6px; width: 250px;" />
<button onclick="callEmbeddedLwc();" style="padding: 6px 14px; cursor: pointer;">
Call LWC Method
</button>
<!-- Mounting container for LWC -->
<div id="lightningOutContainer" style="margin-top: 15px;"></div>
</div>
<script>
var lwcComponentReference = null;
// Initialize Lightning Out
$Lightning.use("c:LwcBridgeApp", function() {
$Lightning.createComponent(
"c:actionHandlerLwc",
{},
"lightningOutContainer",
function(cmp) {
// Query the rendered custom element node in the DOM
lwcComponentReference = document.querySelector("c-action-handler-lwc");
console.log("LWC created and reference captured.");
}
);
});
function callEmbeddedLwc() {
var payloadText = document.getElementById("inputPayload").value;
// Direct call to the public @api method on the custom element
if (lwcComponentReference && typeof lwcComponentReference.performAction === "function") {
lwcComponentReference.performAction(payloadText);
} else {
alert("LWC component is not yet initialized or method not found.");
}
}
</script>
</apex:page>
3. Modern Alternative: Lightning Message Service (LMS)
If your Visualforce page and LWC are deployed as independent components on the same Lightning page layout, you do not need Lightning Out. Use Lightning Message Service instead:
- In Visualforce: Publish data to a shared message channel:
var payload = { actionName: 'REFRESH_DATA', recordData: 'Acme Corp' }; sforce.one.publish($Resource.MyMessageChannel, payload); - In LWC: Subscribe to the channel using
subscribe()fromlightning/messageServiceand trigger your internal method when a message arrives.
4. Common Traps & Development Best Practices
LWC components run exclusively on the client side in the user's browser. Server-side Apex controllers (like
MyVisualforceController.cls) cannot inspect or execute front-end JavaScript methods. All interaction between Visualforce and LWC must happen client-side using JavaScript or Lightning Message Service.
Invoking the LWC method immediately after
$Lightning.use() starts will fail because component creation is asynchronous. Always ensure your method calls are guarded by checking that the DOM reference is defined (if (lwcComponentReference)).
- Always Include
<apex:includeLightning />: Omitting this tag from your Visualforce page prevents the global$Lightningruntime object from loading. - Manage Session Performance: Lightning Out loads the core Lightning component framework inside Visualforce, which adds initial page weight. Cache resources where possible and avoid nesting multiple redundant Aura container apps.
Summary
Bridging Visualforce and Lightning Web Components allows development teams to modernize legacy Salesforce user interfaces incrementally. By leveraging Lightning Out to instantiate components and calling public @api methods directly from Visualforce JavaScript, you can build unified, interactive experiences across both frameworks.