Skip to main content

How to Call LWC Methods from Visualforce in Salesforce: Lightning Out & LMS Guide

In plain words: Calling an LWC method from a Visualforce page allows legacy Visualforce layouts to trigger actions inside modern Lightning Web Components. Because Visualforce and LWC run on different runtime engines, communication is handled on the client side using Lightning Out with public @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 @api Methods (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 @api decorator.
  • Pattern 2: Lightning Message Service (LMS) (Decoupled Messaging): Visualforce publishes a message to a LightningMessageChannel using sforce.one.publish(), and the embedded LWC listens to the channel and executes its internal methods automatically upon receiving the payload.
360 Visualforce-to-LWC Architecture Card:
  • 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.

Step 1: Create the LWC with an @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);
    }
}
Step 2: Define the LWC Markup (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>
Step 3: Create the Aura Dependency App (LwcBridgeApp.app)
Declares the component dependencies required by Lightning Out.
<aura:application access="GLOBAL" extends="ltng:outApp">
    <aura:dependency resource="c:actionHandlerLwc"/>
</aura:application>
Step 4: Build the Visualforce Page (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() from lightning/messageService and trigger your internal method when a message arrives.

4. Common Traps & Development Best Practices

Apex Controller Trap: Attempting to Call LWC Methods via Apex Code
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.
Timing Trap: Calling the Component Before DOM Rendering Completes
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)).
Core Rule: Decorate methods with @api in LWC to expose them publicly, embed via Lightning Out using an Aura dependency app (extends="ltng:outApp"), and invoke the method directly on the custom element DOM node in Visualforce JavaScript.
  • Always Include <apex:includeLightning />: Omitting this tag from your Visualforce page prevents the global $Lightning runtime 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.