Lightning Message Service (LMS) provides a standardized way to communicate between components across different programming models. It works across the DOM, allowing Lightning Web Components, Aura components, and Visualforce pages to exchange messages.
When to Use LMS
Use Lightning Message Service when you need to:
- Communicate between components that don’t have a parent-child relationship
- Send messages between LWC, Aura, and Visualforce
- Broadcast messages to multiple subscribers
- Decouple components from each other
For parent-child communication within LWC, use custom events and public properties instead of LMS.
Creating a Message Channel
Message channels are defined in XML metadata files in the messageChannels folder.
Record_Selected.messageChannel-meta.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>RecordSelected</masterLabel>
<isExposed>true</isExposed>
<description>Message Channel to pass a record Id</description>
<lightningMessageFields>
<fieldName>recordId</fieldName>
<description>This is the record Id that changed</description>
</lightningMessageFields>
</LightningMessageChannel>
Key elements:
<masterLabel>: Display name for the channel
<isExposed>: Set to true to make available in Experience Builder sites
<lightningMessageFields>: Define the data fields in your message
Publishing Messages (LWC)
Components can publish messages to a channel that all subscribers will receive.
lmsPublisherWebComponent.js:
import { LightningElement, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';
// Import message service features required for publishing
import { publish, MessageContext } from 'lightning/messageService';
import RECORD_SELECTED_CHANNEL from '@salesforce/messageChannel/Record_Selected__c';
export default class LmsPublisherWebComponent extends LightningElement {
@wire(getContactList)
contacts;
@wire(MessageContext)
messageContext;
// Respond to UI event by publishing message
handleContactSelect(event) {
const payload = { recordId: event.target.contact.Id };
publish(this.messageContext, RECORD_SELECTED_CHANNEL, payload);
}
}
lmsPublisherWebComponent.html:
<template>
<lightning-card title="LMS Publisher" icon-name="custom:custom30">
<div class="slds-var-m-around_medium" oncontactselect={handleContactSelect}>
<template lwc:if={contacts.data}>
<template for:each={contacts.data} for:item="contact">
<c-contact-list-item-bubbling
key={contact.Id}
contact={contact}
></c-contact-list-item-bubbling>
</template>
</template>
</div>
</lightning-card>
</template>
Key Publishing Concepts
- Import the channel: Use
@salesforce/messageChannel/ChannelName__c
- Wire MessageContext: Provides the context for publishing
- Create payload: Plain JavaScript object with your data
- Publish: Call
publish(context, channel, payload)
Subscribing to Messages (LWC)
Components can subscribe to a channel to receive messages.
lmsSubscriberWebComponent.js:
import { LightningElement, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
// Import message service features required for subscribing
import { subscribe, MessageContext } from 'lightning/messageService';
import RECORD_SELECTED_CHANNEL from '@salesforce/messageChannel/Record_Selected__c';
import NAME_FIELD from '@salesforce/schema/Contact.Name';
import TITLE_FIELD from '@salesforce/schema/Contact.Title';
import PHONE_FIELD from '@salesforce/schema/Contact.Phone';
import EMAIL_FIELD from '@salesforce/schema/Contact.Email';
const fields = [NAME_FIELD, TITLE_FIELD, PHONE_FIELD, EMAIL_FIELD];
export default class LmsSubscriberWebComponent extends LightningElement {
subscription = null;
recordId;
Name;
Title;
Phone;
Email;
@wire(getRecord, { recordId: '$recordId', fields })
wiredRecord({ error, data }) {
if (data) {
fields.forEach(
(item) => (this[item.fieldApiName] = getFieldValue(data, item))
);
}
}
@wire(MessageContext)
messageContext;
// Encapsulate logic for LMS subscribe
subscribeToMessageChannel() {
this.subscription = subscribe(
this.messageContext,
RECORD_SELECTED_CHANNEL,
(message) => this.handleMessage(message)
);
}
// Handler for message received by component
handleMessage(message) {
this.recordId = message.recordId;
}
// Subscribe when component is inserted in the DOM
connectedCallback() {
this.subscribeToMessageChannel();
}
}
Key Subscribing Concepts
- Wire MessageContext: Required for subscribing
- Subscribe in connectedCallback: Ensure subscription is active when component loads
- Handle the message: Process the received payload
- Automatic unsubscribe: Using
@wire(MessageContext) handles unsubscribe automatically
If you subscribe using createMessageContext() instead of @wire, you must manually unsubscribe in disconnectedCallback().
Publishing from Aura Components
Aura components can also publish to message channels.
lmsPublisherAuraComponent.cmp:
<aura:component controller="ContactController">
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<aura:attribute name="contacts" type="Contact[]" />
<!-- Include the message channel -->
<lightning:messageChannel
type="Record_Selected__c"
aura:id="recordSelected"
/>
<lightning:card title="LMS Publisher (Aura)" iconName="custom:custom30">
<div class="slds-var-m-around_medium" oncontactselect="{!c.handleContactSelect}">
<aura:iteration items="{!v.contacts}" var="contact">
<c:contactListItemBubbling
key="{!contact.Id}"
contact="{!contact}"
/>
</aura:iteration>
</div>
</lightning:card>
</aura:component>
lmsPublisherAuraComponentController.js:
({
doInit: function(component, event, helper) {
var action = component.get('c.getContactList');
action.setCallback(this, function(response) {
component.set('v.contacts', response.getReturnValue());
});
$A.enqueueAction(action);
},
handleContactSelect: function(component, event) {
var recordId = event.getParam('contactId');
var message = { recordId: recordId };
component.find('recordSelected').publish(message);
}
})
Message Channel Scope
Lightning Message Service supports two scopes:
Application Scope
Default scope. Messages are delivered to subscribers in the same Lightning application.
import { subscribe, APPLICATION_SCOPE } from 'lightning/messageService';
subscribe(this.messageContext, CHANNEL, this.handleMessage, {
scope: APPLICATION_SCOPE
});
Record Page Scope
Messages are delivered only to subscribers on the same record page.
import { subscribe, RECORD_PAGE_SCOPE } from 'lightning/messageService';
subscribe(this.messageContext, CHANNEL, this.handleMessage, {
scope: RECORD_PAGE_SCOPE
});
Best Practices
Keep Payloads Simple
Send only the data needed. Often just an ID is sufficient:
// Good - send ID and let subscriber fetch details
const payload = { recordId: this.recordId };
// Avoid - sending entire object
const payload = { record: this.entireRecordObject };
Use Descriptive Channel Names
Name channels based on what they communicate, not which components use them:
// Good
Record_Selected__c
Filter_Changed__c
// Avoid
Component_A_To_Component_B__c
Handle Subscription Lifecycle
Always ensure proper cleanup if not using @wire(MessageContext):
import { unsubscribe } from 'lightning/messageService';
disconnectedCallback() {
unsubscribe(this.subscription);
this.subscription = null;
}
Test Message Handling
Mock the message service in your Jest tests:
import { publish } from 'lightning/messageService';
it('handles message', () => {
const element = createElement('c-subscriber', {
is: Subscriber
});
document.body.appendChild(element);
// Publish message
publish(messageContext, CHANNEL, { recordId: '001' });
// Assert component responded
expect(element.recordId).toBe('001');
});
Debugging LMS
Check Channel Configuration
- Verify the channel XML is deployed
- Ensure
<isExposed>true</isExposed> for Experience Cloud sites
- Check that field names match between publisher and subscriber
Verify Import Paths
Channel imports must match the file name:
// File: Record_Selected.messageChannel-meta.xml
import CHANNEL from '@salesforce/messageChannel/Record_Selected__c';
Use Console Logging
Add logging to verify message flow:
handleMessage(message) {
console.log('Received message:', JSON.stringify(message));
this.recordId = message.recordId;
}