Skip to main content

Mastering Salesforce Lightning Message Service (LMS): The Pub-Sub Pattern in LWC

In plain words: The Publish-Subscribe (Pub-Sub) pattern allows distant components that don't share a direct parent-child relationship to talk to each other. In Salesforce, Lightning Message Service (LMS) is the official tool that powers this pattern, letting components across different parts of a page exchange data instantly.

Building rich, dynamic applications in Salesforce often requires multiple components to work together. While custom events handle child-to-parent communication, they fall short when components are located in completely different sections of a page or span across Aura and Lightning Web Component (LWC) boundaries. Lightning Message Service (LMS) solves this challenge by implementing a standardized publish-subscribe messaging infrastructure across the entire Salesforce platform.

1. Understanding the Publish-Subscribe Pattern

The pub-sub messaging paradigm decouples components by separating publishers from subscribers:

  • Publishers: Components that broadcast events and payloads without needing to know who is listening.
  • Subscribers: Components that listen for specific message channels and react automatically when data arrives.
  • Decoupled Architecture: Because components communicate through a centralized message channel, developers can add, remove, or modify components independently without breaking application logic.
360 Lightning Message Service Architecture Card:
  • Scope: Enables communication across LWC, Aura components, and Visualforce pages.
  • Definition File: Custom message channels are defined in XML files ending with .messageChannel-meta.xml.
  • Core Functions: Uses publish() to broadcast messages and subscribe() / unsubscribe() to listen for events.
  • Context Management: Requires importing the message context via @wire(MessageContext).

2. Step-by-Step Implementation of LMS

Step 1: Create the Custom Message Channel Definition
Create an XML metadata file in your Salesforce project under force-app/main/default/messageChannels/:
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>RecordUpdateChannel</masterLabel>
    <isExposed>true</isExposed>
    <description>Message channel to pass selected record updates across unrelated components.</description>
    <lightningMessageFields>
        <fieldName>recordId</fieldName>
        <description>The Id of the selected record.</description>
    </lightningMessageFields>
    <lightningMessageFields>
        <fieldName>recordData</fieldName>
        <description>Additional payload data.</description>
    </lightningMessageFields>
</LightningMessageChannel>
Step 2: Publishing Messages from a Component
The publishing component imports the message channel and publishes payloads using publish().
// publisherComponent.js
import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import RECORD_UPDATE_CHANNEL from '@salesforce/messageChannel/RecordUpdateChannel__c';

export default class PublisherComponent extends LightningElement {
    @wire(MessageContext)
    messageContext;

    handlePublishClick() {
        const payload = { recordId: '0018W00002E3XYZ', recordData: 'High Priority Lead' };
        publish(this.messageContext, RECORD_UPDATE_CHANNEL, payload);
    }
}
Step 3: Subscribing to Messages in Another Component
The subscriber component listens to the channel using subscribe() and cleans up subscriptions using unsubscribe().
// subscriberComponent.js
import { LightningElement, wire, track } from 'lwc';
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from 'lightning/messageService';
import RECORD_UPDATE_CHANNEL from '@salesforce/messageChannel/RecordUpdateChannel__c';

export default class SubscriberComponent extends LightningElement {
    @track receivedRecordId = 'Waiting for input...';
    subscription = null;

    @wire(MessageContext)
    messageContext;

    connectedCallback() {
        this.subscribeToMessageChannel();
    }

    disconnectedCallback() {
        this.unsubscribeFromMessageChannel();
    }

    subscribeToMessageChannel() {
        if (!this.subscription) {
            this.subscription = subscribe(
                this.messageContext,
                RECORD_UPDATE_CHANNEL,
                (message) => this.handleMessage(message),
                { scope: APPLICATION_SCOPE }
            );
        }
    }

    unsubscribeFromMessageChannel() {
        unsubscribe(this.subscription);
        this.subscription = null;
    }

    handleMessage(message) {
        this.receivedRecordId = `Received Record ID: ${message.recordId} (${message.recordData})`;
    }
}

3. Common Traps & Best Practices

Developer Trap: Memory Leaks from Failing to Unsubscribe
When subscribing to a Lightning Message Channel in connectedCallback(), you must unsubscribe in disconnectedCallback(). Failing to clean up active subscriptions when components are destroyed causes memory leaks and duplicate event handlers if the component re-renders.
Core Rule: Use Lightning Message Service (LMS) when communicating between completely unrelated components across LWC and Aura boundaries. For direct parent-child communication, stick to custom events.
  • Choose the Right Scope: Use APPLICATION_SCOPE when you want components across different page regions or utility bars to receive messages.
  • Verify Channel Naming: Custom message channels must be deployed with the __c suffix when referenced in Apex or JavaScript imports (e.g., RecordUpdateChannel__c).
  • Keep Payloads Lightweight: Pass record IDs and lightweight state markers through message channels rather than heavy datasets, letting receiving components fetch detailed records via Lightning Data Service.

Summary

Lightning Message Service revolutionizes cross-component communication in Salesforce. By implementing the publish-subscribe pattern with custom message channels, developers can build decoupled, modular, and scalable applications that allow LWC, Aura, and Visualforce components to collaborate effortlessly.