Skip to main content
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

  1. Import the channel: Use @salesforce/messageChannel/ChannelName__c
  2. Wire MessageContext: Provides the context for publishing
  3. Create payload: Plain JavaScript object with your data
  4. 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

  1. Wire MessageContext: Required for subscribing
  2. Subscribe in connectedCallback: Ensure subscription is active when component loads
  3. Handle the message: Process the received payload
  4. 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

  1. Verify the channel XML is deployed
  2. Ensure <isExposed>true</isExposed> for Experience Cloud sites
  3. 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;
}

Build docs developers (and LLMs) love