Back to the blog

Leverage Lightning Message Service to communicate between VF and LWC

Sep 03 · min read

In Winter 20, Salesforce is releasing “Lightning Message Service“ (LMS), a new feature that allows developers to communicate very easily between VF and Lightning. LMS cuts down the time it takes today to communicate between these platforms by providing a quick channel to which consumers can subscribe and get both updates and convenient tags/import modules, so developers don’t have to worry about CometD resources and/or complicated Javascript.

In this article, I will share with you how to create a LMS channel and then write code in Aura, LWC and Visualforce both to send and receive messages.

To follow the code examples in this post before Winter 20 is live, you’re going to need either a Pre-Release sandbox or a Pre-Release Scratch Org. You can obtain a Pre-Release sandbox from here. Pre-Release Scratch Orgs can be obtained by setting the option release=”Preview” in your scratch org definition.

Pre-Requisite: Create the Message Channel

Before starting our code, we have to create a Message Channel. As of right now, the only way to do this is by leveraging the Metadata API. I’m sure Salesforce will eventually provide a nice UI for it, but right now we have to do a little work.

For this, I am going to use the Workbench. First, we’re going to create a zip file that contains both a package.xml and a file defining the name and fields for our Message Channel. This file should be inside of a folder called messageChannels, and the format for the filename is CHANNELNAME.messageChannel. In our case, that means SayWhat.messageChannel. The package.xml should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="">

Our Message Channel looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="">
   <description>This Lightning Message Channel sends information from VF to LWC and back.</description>
       <description>The message to communicate to others</description>
       <description>Who Is Saying This?</description>


Now you’re ready to create the zip. (Note to Mac users, you may need to run remove bad files via zip -d "__MACOSX*" as this process would fail if these exist).

Go to the following screen in workbench, click “Deploy” and then follow the directions. At the end, you’ll have a properly deployed Message Channel!



 We’re going to handle Visualforce first. In the markup, we’re going to put a simple textbox/button combination to send messages, and two buttons/textbox to handle the subscribe/unsubscribe and reading of messages.

   <p>Message To Send</p>
   <input type="text" id="theMessage" />
   <button onclick="publishMessage()">Send Message</button>
   <p>Subscribe to Say What (must subscribe to receive messages)</p>
   <button onclick="subscribeMC()">Subscribe</button>
   <p>Unsubscribe from Say What</p>
   <button onclick="unsubscribeMC()">Unsubscribe</button>
   <p>Latest Received message:</p>
   <textarea id="ReceivedMessage" rows="5" style="disabled:true;resize:none;width:100%;"/>


Then, underneath, we’ll add the Javascript to make this all happens. The first step is to declare a couple of variables that will be used throughout the sample. Note the use of $MessageChannel, a new object available in VF that enables us to get to the right GUID.

// Load the MessageChannel token in a variable
var SAYWHATMC = "{!$MessageChannel.SayWhat__c}";
var subscriptionToMC;


To publish a message to the channel, we create a JS function that creates a JSON object, and then we call the function with it as a parameter. Note that the properties of my JSON object match the names of the fields I included in the Message Channel, and that none of them uses __c.

function publishMessage() {
   const payload = {
       sourceSystem: "VF",
       messageToSend: document.getElementById('theMessage').value
   };, payload);


To subscribe and unsubscribe, we use a similar technique. We simply make a call to a method in the namespace. Note that the subscribe method returns an object that we will need to unsubscribe later.

function subscribeMC() {
   if (!subscriptionToMC) {
       subscriptionToMC =, onMCPublished, {scope: "APPLICATION"});

function unsubscribeMC() {
   if (subscriptionToMC) {;
       subscriptionToMC = null;


Note the callback function on the subscribe method. The parameter on that event will contain a JSON object with identical shape to the one I used to publish.

function onMCPublished(message) {
   var textArea = document.querySelector("#ReceivedMessage");

   textArea.innerHTML = message ? 'Message: ' + message.messageToSend + '. Sent From: ' + message.sourceSystem : 'no message payload';


The front end in LWC will look very similar to the VF page, so we won’t bore you with HTML. Let’s take a peek at the JS, however, which is where the fun part is.

First, we have to ensure that we import both the methods to interact with LMS, as well as the channel itself, using the @salesforce/messageChannel path.

import { publish,createMessageContext,releaseMessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import SAYWHATMC from "@salesforce/messageChannel/SayWhat__c";


You will notice big similarities between how the VF pub/sub works and how the LWC one does. Let’s look at the top of the page.

context = createMessageContext();

   constructor() {


First we make sure we create a message context. This is important as we’ll use that context to interact with LMS. We do this at the top of the class.

The subscribe and unsubscribe methods do not change very much. The main difference here is that, in a callback function, we don’t have access to the this object that contains our reactive variables. To work around that, we use the callback function inline and refer to our parentPage variable to hold them. Also note that, just like in VF, the event property contains an object of identical shape as the one published.

handleSubscribe() {
       const parentPage = this; = subscribe(this.context, SAYWHATMC, function (event){
           if (event != null) {
               const message = event.messageToSend;
               const source = event.sourceSystem;

               parentPage.receivedMessage = 'Message: ' + message + '. Sent From: ' + source;
   , {scope: APPLICATION_SCOPE}}

   handleUnsubscribe() {


The publishing mechanism is also unchanged save for the addition of the context variable as a parameter for the function call.

publish() {
       const payload = {
           sourceSystem: "lwc",
           messageToSend: this.myMessage

       publish(this.context, SAYWHATMC, payload);


To avoid having “ghost” contexts, it’s best practice to simply release it when the component is removed from the DOM.

disconnectedCallback() {


Aura is a bit different from LWC and VF. Salesforce did not include a subscribe / unsubscribe functionality there, instead doing it automatically for us when we place the lightning:messageChannel component on the page. The onMessage event simply will let us know when something has been published.

In our markup, this is the relevant line:

<lightning:messageChannel type="SayWhat__c" aura:id="sayWhatChannel" onMessage="{!c.handleReceiveMessage}" scope="APPLICATION"/>


Javascript is nearly as simple. To publish we simply construct a JSON object (see the pattern here?) and pass it to the publish method of the component we defined in markup.

let myMessage = component.get("v.myMessage");

const payload = {
   sourceSystem: "Aura",
   messageToSend: myMessage



Receiving is a little different. Here, the JSON’s properties come as the event variable’s parameters, so we have to use getParam to get the values into variables we can use. Using event.propertyname will return an undefined value.

handleReceiveMessage: function (component, event, helper) {
   if (event != null) {
       const message = event.getParam('messageToSend');
       const source = event.getParam('sourceSystem');

       component.set("v.receivedMessage", 'Message: ' + message + '. Sent From: ' + source);

UPDATE (5/4/2020)

In Summer ‘20, this feature went GA and it changed to add the option “scope” parameter. “APPLICATION” is the only allowed value so far. The code snippets above have changed accordingly to show the usage of this parameter.


Here is a little video of the solution in action


In summary, LMS provides us with a very quick and easy way to move complex data across the DOM between VF and LWC/Aura, allowing for better interoperability. There are many cost-saving opportunities in retrofitting old VF pages to work with new Lightning components.

You can find all the code used in this article in this repo:



theCodery understands the challenges in modern tech stacks. We have developed a personalized approach for each Salesforce Cloud implementation while leveraging our deep been-there-done-that and best-practice expertise to ensure you get the most value from your Salesforce deployment.  We take an agile approach with all development, optimization, and integration projects.  Whether you are trying to broaden your engineering and development capabilities, reduce technical debt, integrate tools you are unfamiliar with, or create new applications, theCodery has a proven track record of solving problems and streamlining complexity.

If you have any questions for theCodery about our team, our process, or the clients, please reach out to us at:

theCodery: Accelerate your time-to-value on Salesforce with a trusted partner that delivers scalable architectures that are tailored to delight your customers.

Other Articles by this Author

theCodery’s Dreamforce Recap – 5 Sessions We Found Most Inspiring theCodery’s Dreamforce Recap – 5 Sessions We Found Most Inspiring
Dreamforce 2021 was held in San Fransisco on September 21st-24th. It was a flurry of presentations, meetings, and activities all centered around the Salesforce ecosystem. Th...
5 min read
We Make Migrating from Klaviyo to Marketing Cloud Easy We Make Migrating from Klaviyo to Marketing Cloud Easy
It may look like a mountainous task from the outside, but the integrations experts at theCodery made it look easy. Learn how we tackled the mountainous task of migrating an ...
6 min read
Is Your Salesforce Driving Operations Or Is It In Need Of An Operation? Is Your Salesforce Driving Operations Or Is It In Need Of An Operation?
A great Salesforce system is constantly evolving to increase performance and drive organizational change. A great Salesforce system is not exclusively consumed with just fix...
5 min read
Salesforce Summer 21’ Release Notes Salesforce Summer 21’ Release Notes
Summer 21’ is just around the corner with releases starting across Salesforce instances as soon as May 15th and deploying to all Salesforce orgs by June 12th...The team at t...
5 min read
theCodery Supports Autism Awareness Month theCodery Supports Autism Awareness Month
Here at theCodery, we are part of a program called Pledge 1%. “Pledge 1% is a global movement that encourages and empowers companies of all sizes and stages to donate 1% of ...
3 min read
What Sets theCodery Apart From Other Salesforce Partners? What Sets theCodery Apart From Other Salesforce Partners?
What sets us apart from every partner that I’ve worked for (or cleaned up after) wasn’t pointed out to me during the interview process, employee orientation, or a pep-rally....
5 min read

Get a {FREE} Consultation Now!