Have you ever opened the same web app in two browser tabs and noticed that they behave like two completely separate applications?
Sometimes that's fine.
But sometimes you want an action in one tab to be immediately reflected in the others.
You could use a server, WebSockets, polling, or even localStorage.
But for simple communication between browser tabs, JavaScript already provides a native API:
BroadcastChannel.
Let's see how it works by building a very small counter that stays synchronized across multiple tabs.
The HTML
We only need a counter and a button:
<h2 id="counter">0</h2>
<button id="increment">+1</button>
Nothing special so far.
Each browser tab will have its own JavaScript state:
let count = 0;
Without any synchronization, clicking the button in one tab would only update that tab.
Let's fix that.
Create a BroadcastChannel
First, create a channel:
const channel = new BroadcastChannel("counter");
The string "counter" is simply the name of the channel.
Any tab from the same origin that creates a BroadcastChannel with the same name can communicate with the others.
Now let's implement our counter.
const channel = new BroadcastChannel("counter");
let count = 0;
const counter = document.querySelector("#counter");
const button = document.querySelector("#increment");
button.onclick = () => {
count++;
counter.textContent = count;
channel.postMessage(count);
};
Every time we click the button, we increase the counter and then send the new value through the channel:
channel.postMessage(count);
But the other tabs still need to listen for that message.
Receive messages from other tabs
We can do that with the message event:
channel.onmessage = ({ data }) => {
count = data;
counter.textContent = count;
};
And that's basically the whole implementation.
Here is the complete JavaScript:
const channel = new BroadcastChannel("counter");
let count = 0;
const counter = document.querySelector("#counter");
const button = document.querySelector("#increment");
button.onclick = () => {
count++;
counter.textContent = count;
channel.postMessage(count);
};
channel.onmessage = ({ data }) => {
count = data;
counter.textContent = count;
};
Now open the page in two browser tabs.
Click +1 in the first tab:
Tab 1 → 1
Tab 2 → 1
Then click +1 in the second tab:
Tab 1 → 2
Tab 2 → 2
The two tabs are now communicating directly.
No server.
No WebSocket.
No polling.
What is actually happening?
When this line runs:
channel.postMessage(count);
the browser sends the value to the other contexts listening to the "counter" channel.
The other tab receives it here:
channel.onmessage = ({ data }) => {
// data contains the received value
};
One important detail is that a channel does not send the message back to the same BroadcastChannel object that posted it.
That's why we update the current tab ourselves:
counter.textContent = count;
and use BroadcastChannel to update the other tabs.
You can send more than numbers
BroadcastChannel isn't limited to strings or numbers.
You can send objects too:
channel.postMessage({
type: "counter-updated",
value: count
});
And receive them normally:
channel.onmessage = ({ data }) => {
if (data.type === "counter-updated") {
count = data.value;
counter.textContent = count;
}
};
For real applications, this approach is often better because different message types can share the same channel.
Same origin only
There is one important restriction.
BroadcastChannel works between browsing contexts that belong to the same origin.
For example, two tabs opened on:
https://example.com
can communicate with each other.
A page on another domain cannot join that channel.
This makes BroadcastChannel useful for communication between different instances of the same web application.
When is this useful?
A synchronized counter is obviously just a demo.
In a real application, the same technique can be used to:
- refresh data after another tab modifies it
- synchronize application state
- notify tabs when cached data becomes stale
- coordinate background operations
- communicate between tabs, windows and Web Workers
For simple cross-tab communication, it can save you from building a much more complicated solution.
Closing the channel
When you no longer need the channel, you can close it:
channel.close();
For channels that exist for the entire lifetime of a page, this may not be especially important.
But it's useful when channels are created dynamically.
Final thought
BroadcastChannel is one of those small Web APIs that is easy to overlook.
The entire idea comes down to two operations.
Send something:
channel.postMessage(data);
Receive something:
channel.onmessage = ({ data }) => {
// react to the message
};
So the next time you need two tabs of the same web app to communicate, you might not need a server or WebSocket connection.
Sometimes the browser already gives you exactly what you need.
Top comments (0)