BroadcastChannel
What is BroadcastChannel?
Section titled “What is BroadcastChannel?”BroadcastChannel is a browser API that provides a simple publish/subscribe message bus scoped to the same origin. Any tab, iframe, or worker that creates a channel with the same name automatically joins the same group. When one member calls postMessage, every other member receives the message via its onmessage handler.
It is conceptually similar to localStorage’s storage event, but with important differences:
| Feature | BroadcastChannel | storage event |
|---|---|---|
| Message content | Any structured-cloneable value | String only (storage value) |
| Works in workers | Yes (Shared/Service Workers) | No |
| Fires in the sending tab | No | No |
| Requires localStorage write | No — direct | Yes |
Creating and using a channel
Section titled “Creating and using a channel”// Create (or join) a channel by nameconst ch = new BroadcastChannel('my-channel');
// Listen for messagesch.onmessage = (event) => { console.log('Got:', event.data);};
// Send a message to all other membersch.postMessage({ type: 'UPDATE', payload: 42 });
// When done, release resourcesch.close();The name is the only identifier — two pages on the same origin using new BroadcastChannel('chat') are automatically peers. There is no server or registration step.
Event-listener style
Section titled “Event-listener style”You can also use addEventListener instead of the onmessage assignment. This lets you attach multiple listeners:
const ch = new BroadcastChannel('notifications');
ch.addEventListener('message', (e) => { console.log('Handler 1:', e.data);});
ch.addEventListener('message', (e) => { console.log('Handler 2:', e.data);});Closing a channel
Section titled “Closing a channel”Call ch.close() when you no longer need to receive messages. The channel is not shared state — closing in one tab has no effect on other tabs. If you forget to close it, the browser may keep an event-listener reference alive.
const ch = new BroadcastChannel('demo:temp');// ... use it ...ch.close(); // releases internal resourcesRunnable: post and receive on the same channel
Section titled “Runnable: post and receive on the same channel”The demo below creates a channel, attaches a listener, posts a message, and closes after receipt. Because sender and receiver are in the same page the message echoes back — in a real app the listener lives in a separate tab.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
BroadcastChannel | Sends a real-time message to every tab/worker that’s joined the same channel, with no need to write to storage first | No built-in persistence — if no tab is listening when the message is sent, it’s simply lost |
storage event | Reuses a persistence layer you already have (localStorage) as a side effect, with no extra channel to set up | Can only send strings, and never fires in the tab that wrote — you handle serializing/deserializing yourself |
window.postMessage across a window reference | Fine-grained control over the target — sends only to a window you hold a reference to | You must hold each window’s reference yourself, which gets hard with tabs opening and closing dynamically |
Common mistakes
Section titled “Common mistakes”- Assuming BroadcastChannel queues a message for a receiver that joins later — you must join the channel before the message is sent. If the receiving tab hasn’t created its
BroadcastChannelyet, the message is silently lost. - Forgetting to call
ch.close()on component unmount — the channel object and its listener stay in memory, leaking, especially in an SPA that mounts/unmounts often. - Expecting the tab that calls
postMessageto also receive its own event — BroadcastChannel never delivers a message back to the sender, just as thestorageevent never fires in the writing tab. Update the sending tab’s own UI separately.