Before LMS, communicating between sibling components or across different UI frameworks required messy window-event hacks or custom pubsub modules. Lightning Message Service solves this by providing a standardized, secure messaging architecture defined by a metadata-backed Message Channel.
Prerequisites
- A Salesforce Developer Edition org, Sandbox, or Scratch Org.
- Salesforce CLI (
sf) installed and authenticated. - Basic understanding of LWC lifecycle hooks (
connectedCallbackanddisconnectedCallback) and wire adapters.
Step 1: Create the Lightning Message Channel Metadata
In Salesforce, LMS channels are defined as metadata XML files in your project directory rather than client-side JavaScript files. Create the channel folder and XML definition:
force-app/main/default/messageChannels/SampleMessageChannel.messageChannel-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>SampleMessageChannel</masterLabel>
<isExposed>true</isExposed>
<description>Message channel to broadcast data across unrelated components.</description>
<lightningMessageFields>
<fieldName>messageText</fieldName>
<description>The message string passed across components.</description>
</lightningMessageFields>
</LightningMessageChannel>
LightningMessageChannel metadata with <isExposed>true</isExposed>. Import them using the scoped module prefix @salesforce/messageChannel/ChannelName__c.
Step 2: Build the Publisher Component
Create an LWC named lmsPublisher. The publisher imports the publish method and the message channel reference to broadcast payloads.
Markup (lmsPublisher.html):
<template>
<lightning-card title="LMS Publisher" icon-name="utility:broadcast">
<div class="slds-p-around_medium">
<lightning-input
type="text"
label="Enter Message to Broadcast"
value={messageInput}
onchange={handleInputChange}>
</lightning-input>
<lightning-button
class="slds-m-top_small"
variant="brand"
label="Publish Message"
onclick={handlePublish}>
</lightning-button>
</div>
</lightning-card>
</template>
Controller (lmsPublisher.js):
import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import SAMPLE_CHANNEL from '@salesforce/messageChannel/SampleMessageChannel__c';
export default class LmsPublisher extends LightningElement {
messageInput = '';
@wire(MessageContext)
messageContext;
handleInputChange(event) {
this.messageInput = event.target.value;
}
handlePublish() {
const payload = {
messageText: this.messageInput
};
publish(this.messageContext, SAMPLE_CHANNEL, payload);
}
}
Step 3: Build the Subscriber Component
Create an LWC named lmsSubscriber. It uses subscribe inside connectedCallback() and cleans up subscriptions using unsubscribe inside disconnectedCallback().
Markup (lmsSubscriber.html):
<template>
<lightning-card title="LMS Subscriber" icon-name="utility:inbox">
<div class="slds-p-around_medium">
<p class="slds-text-body_regular">
Latest Received Message: <strong class="slds-text-color_success">{receivedMessage}</strong>
</p>
</div>
</lightning-card>
</template>
Controller (lmsSubscriber.js):
import { LightningElement, wire } from 'lwc';
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from 'lightning/messageService';
import SAMPLE_CHANNEL from '@salesforce/messageChannel/SampleMessageChannel__c';
export default class LmsSubscriber extends LightningElement {
receivedMessage = 'No messages received yet.';
subscription = null;
@wire(MessageContext)
messageContext;
connectedCallback() {
this.subscribeToChannel();
}
subscribeToChannel() {
if (!this.subscription) {
this.subscription = subscribe(
this.messageContext,
SAMPLE_CHANNEL,
(message) => this.handleMessage(message),
{ scope: APPLICATION_SCOPE }
);
}
}
handleMessage(message) {
this.receivedMessage = message?.messageText || 'Empty payload received';
}
disconnectedCallback() {
if (this.subscription) {
unsubscribe(this.subscription);
this.subscription = null;
}
}
}
- APPLICATION_SCOPE: Allows components to receive messages across separate browser tabs and non-active sub-tabs within Lightning Console apps.
- Auto Teardown: Using
@wire(MessageContext)automatically associates subscriptions with the component's lifecycle and clears them safely. - Cross-Tech Support: The same
SampleMessageChannel__ccan communicate simultaneously across LWC, Aura (lightning:messageChannel), and Visualforce (sforce.one.subscribe).
Step 4: Expose and Deploy Components
Update the metadata file (.js-meta.xml) for both components to make them available in Lightning App Builder:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
# Deploy message channels and components to your org
sf project deploy start
# Open your org
sf org open
- In Salesforce, navigate to Setup > Lightning App Builder.
- Place both
lmsPublisherandlmsSubscriberanywhere on the same page. - Save, activate, and test real-time decoupled message passing.