Real-Time Communication Between Browser Tabs
Have you ever needed two browser tabs from the same application to talk to each other? Maybe you wanted to sync a user’s login state, push a theme change across every open tab, or coordinate a real-time dashboard so that an action in one window is instantly reflected in another. Traditionally, developers have relied on clunky workarounds like polling localStorage or using a server as a middleman. The Broadcast Channel API offers a cleaner, event-driven solution built right into the browser.
In this article, you will learn what the Broadcast Channel API is, how it works under the hood, and how to use it in practical scenarios, all with vanilla JavaScript, no frameworks needed.
What Is the Broadcast Channel API?
The Broadcast Channel API is a simple browser API that allows different browsing contexts tabs, windows, iframes, or web workers that share the same origin to send messages to each other. Think of it as a named radio channel: any context that tunes into the same channel name will receive every message broadcast on it.
Unlike window.postMessage, which requires a direct reference to the target window, the Broadcast Channel API decouples the sender from the receivers. You do not need to know how many tabs are open or hold references to them. You simply post a message, and every other context listening on the same channel gets it. The API is same-origin only, which means a page on example.com can only communicate with other pages on the same domain not with a different one.
Getting Started: Creating a Channel
Creating a broadcast channel takes a single line of code. You instantiate a new BroadcastChannel object and pass it a string that identifies the channel name.
const channel = new BroadcastChannel('my_app_channel');
That is it. Any other tab, window, or iframe on the same origin that creates a BroadcastChannel with the exact same name string will automatically be connected. There is no handshake, no connection setup, and no server involved.
Sending Messages with postMessage
Once you have a channel instance, you send data using the postMessage method. The method accepts any value that can be cloned using the structured clone algorithm — strings, numbers, objects, arrays, Dates, Maps, Sets, Blobs, and more.
// Sending a simple string
channel.postMessage('Hello from Tab A!');
// Sending an object
channel.postMessage({
type: 'USER_LOGGED_IN',
payload: { username: 'carlos', role: 'admin' }
});
When Tab A executes the code above, every other browsing context that has a BroadcastChannel open with the name ‘my_app_channel’ will receive the message. Importantly, the sender does not receive its own message — only other listeners do.
Receiving Messages with the onmessage Event
To listen for incoming messages, you attach an event handler to the onmessage property or use addEventListener. The event object contains a data property that holds whatever value was passed to postMessage.
// Using the onmessage property
channel.onmessage = (event) => {
console.log('Received:', event.data);
};
// Or using addEventListener
channel.addEventListener('message', (event) => {
console.log('Received:', event.data);
});
If Tab A sends the object { type: ‘USER_LOGGED_IN’, payload: { username: ‘carlos’, role: ‘admin’ } }, then in Tab B the event.data will be that exact object. The console in Tab B would output:
// Console output in Tab B:
// Received: { type: 'USER_LOGGED_IN', payload: { username: 'carlos', role: 'admin' } }
Practical Example 1: Syncing User Logout Across Tabs
One of the most common use cases for the Broadcast Channel API is synchronizing authentication state. When a user logs out in one tab, you want every other open tab to immediately reflect that change.
Here is how you would implement it. In every page of your application, you create a channel and listen for logout events:
// auth-sync.js — include this script on every page
const authChannel = new BroadcastChannel('auth_channel');
// Listen for logout events from other tabs
authChannel.onmessage = (event) => {
if (event.data.type === 'LOGOUT') {
// Clear local session data
sessionStorage.clear();
// Redirect to the login page
window.location.href = '/login';
}
};
// Call this function when the user clicks "Log Out"
function handleLogout() {
// Perform local logout logic
sessionStorage.clear();
// Notify all other tabs
authChannel.postMessage({ type: 'LOGOUT' });
// Redirect this tab too
window.location.href = '/login';
}
When the user clicks “Log Out” on Tab A, the handleLogout function runs. It clears the session, broadcasts a { type: ‘LOGOUT’ } message, and redirects Tab A. Meanwhile, Tab B, Tab C, and any other open tab receive the message, clear their own session data, and redirect to the login page as well. The result is that every tab is logged out simultaneously without any server polling or interval timers.
Practical Example 2: Real-Time Theme Synchronization
Another great use case is syncing a user preference, like a dark or light theme, across tabs in real time. Without the Broadcast Channel API, you might poll localStorage with a setInterval, which is wasteful. With a broadcast channel, the update is instant.
// theme-sync.js
const themeChannel = new BroadcastChannel('theme_channel');
// Apply a theme to the page
function applyTheme(theme) {
document.body.className = theme;
localStorage.setItem('theme', theme);
}
// Listen for theme changes from other tabs
themeChannel.onmessage = (event) => {
if (event.data.type === 'THEME_CHANGE') {
applyTheme(event.data.theme);
}
};
// Call this when the user toggles the theme
function toggleTheme() {
const currentTheme = document.body.className;
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
// Apply locally
applyTheme(newTheme);
// Broadcast to other tabs
themeChannel.postMessage({ type: 'THEME_CHANGE', theme: newTheme });
}
Suppose the user has three tabs open. They click a “Toggle Theme” button on Tab A, which calls toggleTheme. Tab A switches to dark mode immediately. At the same time, Tabs B and C receive the message and also switch to dark mode — all in real time with no perceptible delay. The user sees a seamless experience across their entire session.
Practical Example 3: Preventing Duplicate Actions
Imagine a notification system where a user might have several tabs open. You want to show a notification banner only in the tab the user is currently viewing, not in every background tab. You can combine the Broadcast Channel API with the Page Visibility API to coordinate this.
// notification-sync.js
const notifChannel = new BroadcastChannel('notification_channel');
// When a new notification arrives (e.g., from a WebSocket)
function onNotificationReceived(notification) {
if (document.visibilityState === 'visible') {
// This tab is active, so show the notification
displayBanner(notification);
// Tell other tabs not to show it
notifChannel.postMessage({
type: 'NOTIFICATION_HANDLED',
id: notification.id
});
}
}
// Listen for messages from other tabs
notifChannel.onmessage = (event) => {
if (event.data.type === 'NOTIFICATION_HANDLED') {
// Another tab already handled it; remove it from our queue
removeFromQueue(event.data.id);
}
};
In this example, when a notification arrives, the active (visible) tab displays it and broadcasts a NOTIFICATION_HANDLED message. All background tabs receive this and remove the notification from their local queue, preventing the user from seeing duplicate banners when they switch tabs.
Closing a Channel
When you no longer need a channel, you should close it to free up resources. This is done with the close method.
channel.close();
After calling close(), the channel will no longer send or receive messages. It is a good practice to close channels when the page is about to unload or when the feature using the channel is deactivated. You can also listen for the beforeunload event to ensure cleanup:
window.addEventListener('beforeunload', () => {
channel.close();
});
Error Handling with messageerror
The Broadcast Channel API provides a messageerror event that fires if a received message cannot be deserialized. This is rare but can happen if there is a problem with the structured clone process.
channel.onmessageerror = (event) => {
console.error('Failed to deserialize message:', event);
};
In practice, this error is uncommon since the structured clone algorithm handles most JavaScript data types. However, it is a good defensive practice to attach this handler so you are aware of any issues in production.
Browser Support and Limitations
The Broadcast Channel API enjoys broad support in all modern browsers, including Chrome, Firefox, Edge, and Safari (since version 15.4). It is not available in Internet Explorer, but that browser is no longer supported by Microsoft.
There are a few important limitations to be aware of. First, the API is restricted to same-origin communication. If you need to communicate across different origins, you will need to use window.postMessage with a reference to the other window. Second, messages are not persisted. If a tab is not open when a message is broadcast, it will not receive the message later. The API is purely fire-and-forget. Third, there is no built-in way to know how many listeners are connected to a channel or to target a specific tab. Messages are always broadcast to all listeners.
Broadcast Channel vs. Other Cross-Tab Communication Methods
You might wonder how the Broadcast Channel API compares to other cross-tab communication approaches. The storage event on localStorage is one alternative. When one tab writes to localStorage, other tabs can detect the change via the storage event. However, this approach is limited to string data (you have to serialize and parse JSON manually), and it is really a side effect of storage, not a dedicated messaging system.
window.postMessage is another option, but it requires you to hold a reference to the target window (via window.open or window.opener). This makes it more suitable for parent-child window communication rather than broadcasting to an unknown number of tabs.
SharedWorker is a more powerful option that allows multiple tabs to share a single background thread, but it is significantly more complex to set up and is not supported in all browsers consistently.
The Broadcast Channel API sits in a sweet spot: it is simple, purpose-built for broadcast communication, handles structured data natively, and requires virtually no boilerplate.
The Broadcast Channel API is one of those browser APIs that flies under the radar but solves a real problem elegantly. It lets you synchronize state, coordinate actions, and communicate between tabs all with a few lines of vanilla JavaScript. Whether you are building a multi-tab dashboard, synchronizing user preferences, or coordinating authentication state, this API gives you a clean, event-driven solution without the overhead of polling, server roundtrips, or complex workarounds.
The next time you need your browser tabs to talk to each other, give the Broadcast Channel API a try. You might be surprised at how simple and powerful it is.
The Broadcast Channel API was originally published in Client-Side JavaScript on Medium, where people are continuing the conversation by highlighting and responding to this story.