What You'll Learn
How LMS solves the cross-DOM communication problem in Lightning, how to define Message Channels, the publish/subscribe API, the scope levels (application vs page), and when LMS is the right choice versus CustomEvent, wire adapters, or Pub/Sub API.
The Cross-Component Communication Problem
LWC components in the same parent-child hierarchy can communicate cleanly: parents pass data down via properties, children bubble events up. But what about components in completely different parts of the DOM — a component in the sidebar and a component in the main content area of the same Lightning page? They have no shared parent, so standard event bubbling does not work.
Before LMS, teams worked around this using URL parameters, custom events dispatched to the window object, or Apex polling. Lightning Messaging Service (LMS) is the proper solution: a framework-native publish/subscribe bus scoped to either a single page or the entire Lightning application.
Message Channels: The Contract
A Message Channel is a metadata artifact that defines the "topic" and the payload shape of messages. It is the contract between publishers and subscribers. Defining the channel in metadata (rather than as ad-hoc strings) provides discoverability, namespace isolation, and deployment via Metadata API.
// Message Channel metadata file
// AccountSelected.messageChannel-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>Account Selected</masterLabel>
<isExposed>true</isExposed>
<description>Published when a user selects an Account in the list view</description>
<lightningMessageFields>
<fieldName>accountId</fieldName>
<description>Salesforce Id of the selected Account</description>
</lightningMessageFields>
<lightningMessageFields>
<fieldName>accountName</fieldName>
<description>Name of the selected Account</description>
</lightningMessageFields>
</LightningMessageChannel>Publisher and Subscriber Implementation
Publishers import the Message Channel and the publish function from the LMS wire adapter. Subscribers import subscribe, unsubscribe, and register a callback in connectedCallback.
// Publisher LWC
import { LightningElement, wire } from 'lwc';
import { MessageContext, publish } from 'lightning/messageService';
import ACCOUNT_SELECTED from '@salesforce/messageChannel/AccountSelected__c';
export default class AccountListView extends LightningElement {
@wire(MessageContext) messageContext;
handleAccountClick(event) {
const payload = {
accountId: event.currentTarget.dataset.id,
accountName: event.currentTarget.dataset.name
};
publish(this.messageContext, ACCOUNT_SELECTED, payload);
}
}
// Subscriber LWC
import { LightningElement, wire } from 'lwc';
import { MessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import ACCOUNT_SELECTED from '@salesforce/messageChannel/AccountSelected__c';
export default class AccountDetailPanel extends LightningElement {
@wire(MessageContext) messageContext;
subscription = null;
connectedCallback() {
this.subscription = subscribe(
this.messageContext, ACCOUNT_SELECTED,
(message) => this.handleAccountSelected(message)
);
}
disconnectedCallback() {
unsubscribe(this.subscription);
this.subscription = null;
}
handleAccountSelected(message) {
this.currentAccountId = message.accountId;
// load account details
}
}disconnectedCallback. A subscription that is not unsubscribed keeps the subscriber component alive in memory even after it is removed from the DOM, causing a memory leak. This is the most common LMS implementation mistake.
Scope Levels: APPLICATION vs DEFAULT
LMS messages can be scoped to two levels:
- APPLICATION scope: Messages are delivered to all subscribers in the entire Lightning application — across multiple tabs, iframes, and Visualforce in LWC containers. Use for truly global events (user changed active record, navigation event).
- DEFAULT scope (recommended): Messages are delivered only to subscribers on the same Lightning page. More contained and easier to reason about. Prevents unintended subscribers in other tabs from reacting to messages not meant for them.
LMS vs Other Communication Patterns
When to use LMS versus other mechanisms:
- Use CustomEvent: When components are in a parent-child hierarchy. CustomEvents bubble up the component tree naturally.
- Use @wire with reactive properties: When a component needs to reactively fetch data when a parameter changes — no explicit messaging needed.
- Use LMS: When components are siblings with no shared parent — classic list/detail panel pattern on a Lightning App page.
- Use Platform Events: When the communication spans server-side Apex or external systems. Platform Events work outside the Lightning browser context.
- Use Pub/Sub API (lwc-recipes): For non-Salesforce Lightning contexts (LWC OSS, custom web apps) where LMS is not available.
Key Takeaways
- LMS solves cross-DOM communication between unrelated LWC/Aura/VF components on the same Lightning page.
- Message Channels are metadata artifacts — define the contract in a
.messageChannel-meta.xmlfile and deploy via Metadata API. - Always unsubscribe in
disconnectedCallbackto prevent memory leaks. - Use DEFAULT scope unless you explicitly need cross-tab communication — APPLICATION scope is the most common over-use of LMS.
- LMS works only in the Lightning browser context — use Platform Events for server-side or cross-system communication.
- The classic LMS use case is a list component and a detail component on the same App Builder page with no parent-child relationship.
Check Your Understanding
1. A subscriber component is removed from the DOM but does not call unsubscribe(). What is the consequence?
2. Two LWC components are siblings in a Lightning App Builder page with no shared parent. What is the correct communication mechanism?
3. Which LMS scope delivers messages to all subscribers across all open Lightning tabs in the browser session?
Discussion & Feedback