BroadcastChannel API: Cross-Tab Messaging in JavaScript
The BroadcastChannel API lets same-origin tabs, windows, and workers pass messages to each other instantly, with no server round trip or polling.
The BroadcastChannel API lets separate browsing contexts on the same origin — tabs, windows, iframes, and workers — send messages directly to each other. Any context that opens a channel with the same name receives every message posted to it, instantly, with no server, no polling, and no manual wiring between windows. It’s the browser’s built-in answer to a problem every multi-tab web app eventually runs into: keeping state in sync when a user has your site open in more than one place at once.
The problem it solves
Open a banking site in two tabs and log out in one. Without cross-tab coordination, the other tab has no idea anything changed — it still shows a logged-in session until the next request fails. The same gap shows up with shopping carts, unread-message counts, theme toggles, and feature flags: each tab runs its own isolated JavaScript context, and by default none of them know what the others are doing.
Before BroadcastChannel existed, developers worked around this with hacks like writing to localStorage and listening for the storage event, which fires in other tabs when a key changes. That works, but it repurposes a storage mechanism for messaging, persists data you may not want persisted, and only fires in tabs other than the one that wrote the value. BroadcastChannel is a purpose-built alternative: an actual pub-sub channel, not a side effect of storage.
How it works
Any context creates a channel by name, and any other same-origin context that opens a channel with the same name joins it:
const channel = new BroadcastChannel("auth");
// Send a message to every other listener on this channel
channel.postMessage({ type: "logout" });
// Receive messages from other tabs/workers on this channel
channel.onmessage = (event) => {
if (event.data.type === "logout") {
window.location.href = "/login";
}
};
// Release the channel when it's no longer needed
channel.close();
The channel name is just a string both sides agree on — there’s no registration step or server involved. A message posted from one tab is delivered to every other open context on the same origin that has a channel with that name, including the sending page’s other channel instances, but never back to the exact object that sent it. Messages can be almost any structured-cloneable value: objects, arrays, Maps, Sets, even ArrayBuffers — the same set of types the structuredClone algorithm supports, since that’s what the browser uses internally to pass the message between contexts.
What counts as a listener
“Same origin” is the whole scope of a BroadcastChannel. Any document, iframe, dedicated worker, or service worker running on the same scheme, host, and port can join a channel by name — regardless of which tab opened it first, and regardless of whether the pages know about each other in any other way. A channel has no concept of “the page that created it”; it’s a shared namespace, not a connection between two specific endpoints. Close a channel with .close() when a component unmounts or a page unloads, or it stays registered as long as the page is alive.
BroadcastChannel vs the alternatives
| BroadcastChannel | localStorage + storage event | SharedWorker | WebSocket | |
|---|---|---|---|---|
| Scope | Same-origin tabs/workers | Same-origin tabs | Same-origin tabs/workers | Any client connected to the server |
| Needs a server | No | No | No | Yes |
| Persists data | No | Yes (until cleared) | No (in-memory) | No |
| Cross-device sync | No | No | No | Yes |
| Best for | Ephemeral cross-tab events | Legacy fallback, simple flags | Shared in-memory state/computation | Real-time sync across users/devices |
BroadcastChannel and a SharedWorker solve adjacent problems: a SharedWorker gives multiple tabs one shared execution context to hold state and run logic, while BroadcastChannel just moves messages between contexts that already have their own state. Reach for BroadcastChannel when tabs just need to notify each other that something happened; reach for a SharedWorker when tabs need to share actual computation or a single source of truth. For anything that needs to reach other users or other devices — not just other tabs on the same machine — you need a server round trip, typically over a WebSocket or server-sent events.
Common use cases
- Session sync. Broadcast a
logoutorsession-expiredevent so every open tab redirects together, rather than leaving stale tabs in an inconsistent state. - Cart and preference sync. Update a shopping cart or theme setting in one tab and reflect it everywhere else without a page reload.
- Cache invalidation. Tell other tabs to refetch data after a mutation, avoiding duplicate writes or stale reads across tabs.
- Coordinating singleton behavior. Have only one tab perform an expensive background task (like a periodic sync) by having tabs negotiate over a channel, with the others staying idle.
Limitations to know about
BroadcastChannel is same-origin only — it cannot communicate across different domains or subdomains, and there’s no built-in origin-crossing escape hatch (for that, you’d need something like postMessage between windows that already hold references to each other). Messages aren’t durable: a tab that opens a channel late gets nothing that was sent before it joined, so it’s not a substitute for state that must survive a page load — persist that in localStorage or IndexedDB instead and use BroadcastChannel purely as the notification layer on top. And because delivery depends on the browser’s event loop, treat messages as a signal to react to, not a database to depend on for correctness — pair it with a fetch or a re-read of persisted state rather than trusting the message payload as the sole source of truth.
The takeaway
BroadcastChannel is a small, purpose-built API for one job: letting same-origin tabs, windows, and workers notify each other instantly without a server. It beats the old localStorage-event workaround for anything that’s really a message rather than a stored value, and it composes cleanly with persisted storage for state and a server connection for anything that needs to reach beyond the browser. For keeping multiple open tabs of the same app in sync, it’s usually the simplest tool that gets the job done.
Tagged
Keep reading
Takina · · 5 min read Event Bubbling vs Capturing in JavaScript, Explained
Event bubbling and capturing are the two directions a DOM event travels. How propagation phases work, when to use each, and how to stop them.
Takina · · 4 min read Object.groupBy() in JavaScript, Explained
Object.groupBy() buckets array items by a key you compute, replacing the reduce() boilerplate developers have written for years. How it works.
Takina · · 4 min read Web Locks API: Coordinating Work Across Browser Tabs
The Web Locks API lets JavaScript acquire named locks shared across tabs, so only one tab does work like a token refresh or a write at a time.