BroadcastChannel API: Send Messages Across Browser Tabs

March 22, 2026

If you've ever wanted to sync state across multiple tabs — like logging a user out everywhere at once, or syncing a shopping cart — the BroadcastChannel API is the cleanest way to do it.

No server. No WebSockets. Just the browser.

What is the BroadcastChannel API?

The BroadcastChannel API is a simple publish/subscribe mechanism built into the browser. Any browsing context (tab, window, iframe, or worker) on the same origin can join a named channel and send or receive messages on it.

It's conceptually the simplest of the browser communication APIs — even simpler than postMessage. You don't need a reference to the target window. You just open a channel by name, and every other context with the same channel name hears your messages.

// Tab A — send a message
const channel = new BroadcastChannel('app-state');
channel.postMessage({ type: 'USER_LOGGED_OUT' });
// Tab B — receive the message
const channel = new BroadcastChannel('app-state');
channel.onmessage = (event) => {
  console.log(event.data); // { type: 'USER_LOGGED_OUT' }
};

That's it. No event listeners on window. No targetOrigin parameter. No checking the source.

How It Works

When you call new BroadcastChannel('channel-name'), the browser registers this browsing context as a subscriber to that named channel. When you call .postMessage(), the browser delivers the message to every other subscriber on the same channel — but not back to the sender.

Key points:

  • Same origin only — the channel is scoped to the exact origin (protocol + domain + port). You can't broadcast across origins.
  • The sender doesn't receive its own messages — if Tab A sends, Tab B and Tab C get it, but Tab A does not.
  • The data is structured-cloned — you can send objects, arrays, blobs, and even Transferable Objects for zero-copy transfers.

A Real Example: Sync Login State Across Tabs

This is the most common use case. When a user logs out in one tab, all other tabs should reflect that immediately.

// auth.js — shared across all pages

const authChannel = new BroadcastChannel('auth');

export function logout() {
  // Do the actual logout
  clearLocalStorage();
  redirectToLogin();

  // Tell every other tab
  authChannel.postMessage({ type: 'LOGOUT' });
}

// Listen for logout events from other tabs
authChannel.onmessage = (event) => {
  if (event.data.type === 'LOGOUT') {
    redirectToLogin();
  }
};

No polling. No localStorage event hacks. Clean and direct.

Another Example: Sync a Shopping Cart

const cartChannel = new BroadcastChannel('cart');

function addToCart(item) {
  const cart = getCartFromStorage();
  cart.push(item);
  saveCartToStorage(cart);

  // Notify other tabs to refresh their cart UI
  cartChannel.postMessage({ type: 'CART_UPDATED', cart });
}

cartChannel.onmessage = (event) => {
  if (event.data.type === 'CART_UPDATED') {
    renderCart(event.data.cart);
  }
};

Using addEventListener Instead of onmessage

You can also use addEventListener for more flexibility, especially when you want multiple listeners:

const channel = new BroadcastChannel('app-state');

channel.addEventListener('message', (event) => {
  console.log('Received:', event.data);
});

channel.addEventListener('messageerror', (event) => {
  console.error('Failed to deserialize message:', event);
});

messageerror fires when the browser can't deserialize the received data — rare, but worth handling.

Closing a Channel

When you no longer need the channel, close it to free resources:

channel.close();

After closing, any call to .postMessage() on that instance throws an InvalidStateError. You'd need to create a new BroadcastChannel instance to reconnect.

BroadcastChannel vs postMessage

| Feature | BroadcastChannel | postMessage | |---|---|---| | Target | All tabs on same origin | Specific window/iframe | | Needs target reference | No | Yes | | Cross-origin | No | Yes (with targetOrigin) | | Workers | Yes | Yes | | Simplicity | Very simple | Moderate |

Use BroadcastChannel when you want to broadcast to all tabs without knowing who's listening.

Use postMessage when you need to communicate with a specific window or iframe — especially cross-origin. Check out my articles on postMessage with iframes and postMessage with popups for that.

BroadcastChannel vs localStorage events

Before BroadcastChannel existed, a common hack for cross-tab communication was listening to the storage event on window:

// Old hack
window.addEventListener('storage', (event) => {
  if (event.key === 'logout') redirectToLogin();
});

// Trigger it
localStorage.setItem('logout', Date.now());

It works, but it's a hack. You're abusing localStorage as a message bus. BroadcastChannel is the right tool — cleaner API, supports objects natively (no JSON.stringify needed), and you can send to workers too.

Using BroadcastChannel in Web Workers

BroadcastChannel works in Web Workers as well, which is genuinely useful. You can send messages from a worker to all tabs, or from one tab to a worker running in another tab:

// worker.js
const channel = new BroadcastChannel('worker-updates');

// Worker sends progress updates to all tabs
self.onmessage = (e) => {
  doHeavyWork(e.data);
  channel.postMessage({ type: 'PROGRESS', percent: 75 });
};
// main.js (in any tab)
const channel = new BroadcastChannel('worker-updates');
channel.onmessage = (e) => {
  updateProgressBar(e.data.percent);
};

Sending Large Data Efficiently

By default, BroadcastChannel uses structured cloning — the data is copied. For large payloads like ArrayBuffers or ImageBitmaps, you can pass them as Transferable Objects instead. Transferables are moved (zero-copy), not cloned, which is significantly faster.

const buffer = new ArrayBuffer(1024 * 1024); // 1MB
const channel = new BroadcastChannel('data-pipe');

// Transfer the buffer instead of copying it
channel.postMessage({ buffer }, [buffer]);
// buffer is now detached in this context — it belongs to the receiver

Browser Support

BroadcastChannel is supported in all modern browsers — Chrome, Firefox, Safari (15.4+), and Edge. It's not supported in IE (but you're not supporting IE in 2026, right?).

When to Use BroadcastChannel

✅ Syncing login/logout state across tabs
✅ Syncing theme preference (dark/light mode) without a page reload
✅ Invalidating a cache in all tabs after a data mutation
✅ Real-time collaboration (basic — for serious cases, use WebSockets)
✅ Sending progress updates from a shared Web Worker to all tabs

❌ Cross-origin communication (use postMessage)
❌ Sending to a specific tab (no way to target one tab — use postMessage + a shared worker instead)
❌ Persistent messaging (messages are fire-and-forget — if a tab isn't open, it misses the message)

Summary

  • new BroadcastChannel('name') subscribes to a named channel
  • .postMessage(data) broadcasts to all other subscribers on the same origin
  • The sender doesn't receive its own messages
  • Close with .close() when done
  • Works in tabs, iframes, and Web Workers
  • Cleaner than the localStorage storage event hack
  • For sending to a specific window/iframe, use postMessage instead
  • For zero-copy large data transfers, use Transferable Objects