← Back to Platform & Technical
PLAT-022 Platform & Technical 18 min read For: Architects

Lightning Messaging Service: Loosely Coupled Component Communication

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.

VS

Vishal Sharma

Salesforce Architecture Specialist · Updated May 2026

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.

Insight LMS is specifically for the Lightning Experience environment. It works across LWC, Aura, and Visualforce in LWC containers on the same page. If you are building communication between standalone pages or external systems, LMS is not the right tool — use Platform Events or the Pub/Sub API.

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
  }
}
Key Point Always unsubscribe in 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.xml file and deploy via Metadata API.
  • Always unsubscribe in disconnectedCallback to 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?

A. The subscription automatically expires after the component is garbage collected
B. The subscription keeps the component in memory, causing a memory leak — always unsubscribe in disconnectedCallback
C. LMS throws an error when the component is removed without unsubscribing

2. Two LWC components are siblings in a Lightning App Builder page with no shared parent. What is the correct communication mechanism?

A. CustomEvent with bubbles:true — events bubble to the root and the sibling can listen there
B. Lightning Messaging Service — it is designed specifically for communication between unrelated components on the same page
C. Platform Events — they work across all Salesforce components regardless of hierarchy

3. Which LMS scope delivers messages to all subscribers across all open Lightning tabs in the browser session?

A. DEFAULT scope — it routes to all active subscribers
B. APPLICATION scope — it delivers to the entire Lightning application including multiple tabs
C. GLOBAL scope — a special scope for cross-tab broadcasting

Discussion & Feedback