10LOC
Web Platformintermediate

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) calls postMessage, which delivers { type: "logout", reason } to every other context on the channel. A channel never delivers a message back to the sender itself.
  • The message listener 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() calls channel.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 postMessage to 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

  • storage event — the older, string-only, localStorage-piggybacking way to notice cross-tab changes; BroadcastChannel replaces 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.