BroadcastChannel for cross-tab messaging
Published October 27, 2026
const channel = new BroadcastChannel("auth");
export const broadcastLogout = (reason: string) => channel.postMessage({ type: "logout", reason });
channel.addEventListener("message", (event: MessageEvent<{ type: string; reason: string }>) => {
if (event.data.type !== "logout") return;
localStorage.removeItem("auth-token");
location.assign(`/login?reason=${encodeURIComponent(event.data.reason)}`);
});
export const closeAuthChannel = () => channel.close();What
BroadcastChannel is a same-origin publish/subscribe channel: any tab, window, or worker that opens a channel with the same name can send messages that every other listener on that name receives, instantly, without a server.
Why it matters
The traditional way to notice a change made in another tab is a storage event — but it only fires for localStorage writes, only fires in tabs other than the one that wrote, and forces you to encode every message as a string diff on a key you're otherwise not using for messaging. It works, but it's a repurposed side effect, not a real messaging primitive.
BroadcastChannel is built for exactly this: structured-cloneable messages (objects, not just strings), a real message event, and delivery to every other same-origin context listening on that channel name, including workers. The canonical case is auth: log out in one tab, and every other tab holding a stale session should drop it immediately rather than let the user keep interacting with a page that's about to 401.
How it works
new BroadcastChannel("auth")opens (or joins) a channel named"auth". Any other same-origin context that also opens"auth"is now a listener — no explicit connection step.broadcastLogout(reason)callspostMessage, which delivers{ type: "logout", reason }to every other context on the channel. A channel never delivers a message back to the sender itself.- The
messagelistener runs in each of those other tabs: it clears the locally cached token and redirects to/login, carrying the reason through as a query param. closeAuthChannel()callschannel.close(), detaching this context from the channel. Call it on page teardown if you're creating channels dynamically — a channel left open on a page kept alive in a bfcache/back-forward state keeps listening.
Gotchas
- Same-origin only, and same-browser-profile — it does not cross domains, and it does not reach other devices. This is a same-machine, same-origin mechanism, not a substitute for a server push.
- Messages are structured-cloned, same as
postMessageto a worker: functions, DOM nodes, and class instances with methods don't survive the trip. Plain objects, arrays, and primitives do. - A channel doesn't buffer messages for listeners that haven't opened it yet. A tab that opens
BroadcastChannel("auth")after the logout message was sent will never see that message.
Related
storageevent — the older, string-only,localStorage-piggybacking way to notice cross-tab changes;BroadcastChannelreplaces it for anything that isn't already a storage write.- The Web Locks API — for coordinating which tab does something, rather than announcing that something happened; the two are often used together.