Tested in Summer '26. Salesforce changes often. If something here no longer matches your org, tell us and we will retest it.

Straight answer: Lightning Message Service is how two components that share no DOM ancestry talk to each other, and the thing that breaks the first deploy is almost always the import. Your channel file is named TaRecordPicked.messageChannel-meta.xml. The import specifier is @salesforce/messageChannel/TaRecordPicked__c. That suffix appears nowhere in the file you wrote.

Everything here was deployed to my own Developer Edition org on API 67.0, which is Summer ’26.

Which one the page actually needsA message channel is the right answer only when the components have no relationship in the DOM.A custom event will doThe two components share a DOM ancestorThe event travels up to a parent thathandles itThe coupling is visible in the parent'smarkupTestable without mocking the message serviceNothing to name, nothing to registerOnly a message channel will doThere is no shared ancestor to catch an eventUtility bar component talking to a recordpageAn LWC talking to Aura or Visualforce on onepageNeeds APPLICATION_SCOPE to cross navigationareasNeeds unsubscribe in disconnectedCallbackThe import carries a suffix the file name does not: TaRecordPicked.messageChannel-meta.xml is importedas TaRecordPicked__c. Miss it and the deploy fails on an Aura descriptor error.
Reach for a channel only when there is no shared ancestor left to catch an event.

The channel is metadata, not code

A message channel is a small XML file under force-app/main/default/messageChannels/. It declares the fields a message carries and whether components outside your namespace may use it.

<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>Carries the picked record id between unrelated components</description>
    <isExposed>true</isExposed>
    <lightningMessageFields>
        <fieldName>recordId</fieldName>
        <description>Id of the record the user picked</description>
    </lightningMessageFields>
    <masterLabel>TaRecordPicked</masterLabel>
</LightningMessageChannel>

The lightningMessageFields entries are documentation rather than enforcement. Nothing stops you publishing a payload with different keys, and nothing validates the shape at runtime, so the declaration is a contract you are choosing to honour.

The error you get when the import is wrong

I deployed a subscriber importing the channel without the suffix, the way the file name suggests. The deploy failed, and the message is not one you would guess your way back from.

Status: Failed
LightningComponentBundle  taLmsBad
org.auraframework.throwable.AuraRuntimeException: Unable to retrieve metadata
for descriptor: markup://c:__LightningMessageChannel__TaRecordPicked

Nothing in that sentence mentions the missing __c. It reports an Aura descriptor, in an org where you wrote no Aura, for a component you did not name. The same file with TaRecordPicked__c in the import deployed first time.

That is the whole fix. If a deploy fails with an Aura descriptor error naming your channel, check the suffix before you check anything else. People have been filing this against sample repos since message channels shipped, so you are in good company.

Publishing and subscribing

Both sides need MessageContext, wired in as a property. The publisher then calls publish with the context, the channel and a payload.

import { publish, MessageContext } from 'lightning/messageService';
import PICKED from '@salesforce/messageChannel/TaRecordPicked__c';

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

    handlePick(event) {
        publish(this.messageContext, PICKED, { recordId: event.target.dataset.id });
    }
}

The subscriber side has two details that matter more than they look.

connectedCallback() {
    this.subscription = subscribe(
        this.messageContext,
        PICKED,
        (message) => { this.picked = message.recordId; },
        { scope: APPLICATION_SCOPE }
    );
}

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

The first is APPLICATION_SCOPE. Without it a subscriber only hears messages published within the active navigation area, which is the behaviour you want less often than the default implies. A component in the utility bar listening for something published on a record tab needs application scope or it hears nothing.

The second is unsubscribe in disconnectedCallback. Subscriptions outlive the component otherwise, and in a console app where tabs open and close all day that accumulates. Handlers keep firing against components that are gone.

When this is the right tool

LMS exists for the case where the two components have no relationship in the DOM. A component in the utility bar and a component on a record page cannot reach each other with custom events, because events travel up the DOM tree and there is no shared ancestor to catch them.

Where there is a shared ancestor, use a custom event and a parent handler instead. It is traceable, it is testable without mocking the message service, and the coupling is visible in the markup. LMS moves that coupling into a channel name, which is exactly the kind of indirection that makes a page hard to reason about two years later.

My rule is that a channel needs an owner and a documented payload, or it becomes a global variable with extra steps. Two components either side of a page boundary is a good reason. Laziness about component hierarchy is not.

What I verified and what I did not

I verified the deploy behaviour, both the failure and the fix, in a real org on Summer ’26. I have not put these two components on a Lightning page and watched a message cross the boundary in a browser during this write-up, so the scope behaviour above is from the documentation and from previous projects rather than from a test I can show you today.

I also have not tested LMS inside an Experience Cloud site, where the constraints differ, nor between an LWC and an Aura component, which is supported and which I would want to confirm before relying on it.

Frequently asked

Does LMS work across a Visualforce page? It is supported from Visualforce, Aura and LWC, which is the main argument for it over any homegrown pub/sub module. A page mixing all three is precisely where it pays off.

Is the old pubsub module still fine? The pubsub sample component predates LMS and does not cross the boundaries LMS does. New code should use the platform service.

Can I send a whole record in the payload? You can send any serialisable object. Sending an ID and letting each component wire its own data keeps the components independent and lets Lightning Data Service do the caching, which is usually the better shape.

How do I test a subscriber in Jest? The lightning/messageService module is stubbed by the sfdx-lwc-jest setup, so you assert that subscribe was called with your channel and then invoke the captured handler directly. That is covered in the Jest tests worth writing.