mrkeyoor.com_
Wed 23 Sept 00:36 UTC
npmUtilsupdated 22 Sept 2026

broadcast-channel review

broadcast-channel moves JSON-serializable messages among tabs, frames, workers, or local Node processes that share a channel name. It chooses the native browser API when available, then IndexedDB or localStorage; Node communication goes through filesystem sockets. Its second job is leader election, which lets one tab own a WebSocket or polling loop. Version 7.4.0 fixes a race where an elector could become leader after die() and makes repeated die() calls return the same promise. It also exports the OnMessageHandler TypeScript type. Our package check found bundled declarations and working require() and ESM import paths.

Verdict

broadcast-channel 7.4.0 installed in 1.4 seconds and produced a 5.3 KB gzipped browser bundle in our sandbox, a fair cost when fallback transports or leader election are required. Use the native browser API for simple notifications, and use a broker when messages must cross machines or survive disconnects.

We installed it

Lab card: what happened when we installed broadcast-channelScreenshot of broadcast-channel documentation
Install✓ · 1.4s8 packages on disk · 3 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser5.3 KBgzipped (16.5 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does broadcast-channel install cleanly?

Yes. In a fresh container with an empty cache, npm install broadcast-channel finished in 1 seconds, leaving 8 packages and 3 MB on disk. npm audit reported no known vulnerabilities.

How much does broadcast-channel add to a browser bundle?

5.3 KB gzipped (16.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does broadcast-channel work with both ESM and CommonJS?

Yes. Both import 'broadcast-channel' and require('broadcast-channel') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does broadcast-channel include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

broadcast-channel or @vueuse/core: which should you use?

@vueuse/core: Choose its useBroadcastChannel helper when the application is Vue-only and native browser support is sufficient. broadcast-channel 7.4.0 installed in 1.4 seconds and produced a 5.3 KB gzipped browser bundle in our sandbox, a fair cost when fallback transports or leader election are required.

When should you not use broadcast-channel?

All supported browsers already have native BroadcastChannel and leader election is unnecessary; the platform API avoids four direct dependencies

API stability4/5Version 7.4.0 keeps the long-standing BroadcastChannel constructor, postMessage(), listeners, close(), enforceOptions(), and leader-elector calls. Earlier majors did change observable behavior: version 5 made hasLeader() asynchronous, version 6 added Deno, and version 7 stopped delivering messages created before a channel opened. The package has a steady core, but a major upgrade still deserves a changelog read and a multi-context test.
Docs4/5The README documents all 5 transport types, JSON-only payloads, direct-value listeners, IndexedDB closure recovery, Node temporary-folder cleanup, global test simulation, Web Locks, election tuning, and duplicate-leader handling. Its examples are enough to start and its non-goals are unusually direct. The weak point is release documentation: the changelog ends at 7.3.0, so the 7.4.0 race fix and exported handler type must be found in the merged changes.
Maintenance5/5GitHub showed a push on August 25, 2026, an unarchived repository, 1,997 stars, and 11 open issues and pull requests. npm published 7.4.0 on August 3, 2026. That release added regression coverage for killing an elector during an active election, fixed both the Web Locks and message-based paths, and made shutdown repeatable. The repository also tests browser, Node, Deno, ESM, CommonJS, and declaration output.
Ecosystem4/5npm counted 3,375,865 downloads for the week ending August 24, 2026. One package covers native channels, IndexedDB, localStorage, Node filesystem sockets, Deno, Web Workers, iframes, and an isolated test transport. Our install confirmed both module-loading styles and bundled types. Its boundary is just as important: there is no cross-host adapter, authentication protocol, durable log, acknowledgement system, or binary message format.

Use it if

  • Old and current browsers must exchange tab or worker notifications through one API
  • One browser tab should own a shared WebSocket, polling loop, or scheduled refresh
  • Local Node processes need low-rate coordination without a Redis service
  • Tests need a deterministic in-process transport that keeps the production channel API
Skip it if

Setup reality

Our clean install of broadcast-channel 7.4.0 finished in 1.4 seconds on Node 22. It left 8 packages and 3 MB on disk, while npm audit reported 0 known vulnerabilities. The package itself is 836 KB unpacked with 4 direct dependencies and no peers. It is published as CommonJS with an exports map; require() and ESM import both worked, and TypeScript declarations are included. Our full browser import measured 16.5 KB minified and 5.3 KB gzipped.

No account, secret, or config file is involved. Transport choice is the first surprise: current browsers normally use native BroadcastChannel, with IndexedDB and localStorage as fallbacks. Setting webWorkerSupport to false removes IndexedDB from consideration, which can help a tab-only case but excludes workers. Mobile Safari and cleared site storage can close IndexedDB unexpectedly. The documented recovery is to close that channel and construct another from the idb.onclose callback.

Messages have no replay and the sender does not receive its own post. Values must survive JSON.stringify, and a listener gets the value directly rather than event.data. Await close() so pending sends finish and storage or socket resources are released. In Node, the filesystem transport uses temporary directories. The defaults include a 2-minute TTL and a 2,048-write parallel cap; large test suites should call clearNodeFolder() before opening their channels, never while production peers are active.

Leader election uses Web Locks where the runtime supplies them and falls back to channel messages elsewhere. Version 7.4.0 prevents an elector killed during an in-flight election from later claiming leadership, and die() is now idempotent. Duplicate leaders can still occur when a browser throttles timers or the CPU is saturated, so make elected work safe to repeat and implement onduplicate. The simulate transport is fast for tests but cannot communicate outside its one JavaScript process; enforceOptions() is global and needs resetting after each suite.

Patterns

Exchange a typed session event send-typed-message

import { BroadcastChannel } from 'broadcast-channel'

type SessionEvent =
  | { type: 'signed-out' }
  | { type: 'profile-updated'; userId: string }

const channel = new BroadcastChannel<SessionEvent>('app-session')
channel.onmessage = (event) => {
  if (event.type === 'signed-out') redirectToLogin()
}

await channel.postMessage({ type: 'profile-updated', userId: 'u_42' })

Version 7.4.0 passes the posted value to onmessage, so event.data is undefined here. The runtime payload must also survive JSON.stringify.

Attach two independent consumers manage-listeners

const channel = new BroadcastChannel('inventory')
const refresh = (message) => updateInventory(message)
const record = (message) => console.debug(message)

channel.addEventListener('message', refresh)
channel.addEventListener('message', record)
channel.removeEventListener('message', record)

addEventListener keeps both callbacks active. Assigning onmessage a second time replaces the first onmessage callback.

Finish pending sends during shutdown close-channel

const channel = new BroadcastChannel('jobs')

try {
  await channel.postMessage({ type: 'job-finished', id: job.id })
} finally {
  await channel.close()
}

close() returns a promise that settles after queued sends and transport cleanup. A later postMessage() throws.

Report the selected transport inspect-transport

const channel = new BroadcastChannel('diagnostics')

console.log({
  name: channel.name,
  transport: channel.type,
  closed: channel.isClosed,
})

The 5 built-in transport names are native, idb, localstorage, node, and simulate. Runtime support and options decide the result.

Reopen after IndexedDB closes recover-indexeddb

let channel

function openChannel() {
  channel = new BroadcastChannel('sync', {
    idb: {
      onclose: async () => {
        await channel.close()
        openChannel()
      },
    },
  })
  channel.onmessage = handleSync
}

openChannel()

Mobile Safari and cleared site storage can close IndexedDB. The README calls for closing the wrapper before creating its replacement.

Limit an old-browser channel to tabs disable-worker-support

const channel = new BroadcastChannel('tab-events', {
  webWorkerSupport: false,
})

channel.onmessage = handleEvent

With webWorkerSupport false, method selection excludes IndexedDB. Workers cannot participate, and an old browser may fall back to localStorage.

Use the single-process test method simulate-in-tests

import { enforceOptions } from 'broadcast-channel'

beforeEach(() => enforceOptions({ type: 'simulate' }))
afterEach(() => enforceOptions(null))

enforceOptions changes every channel in the process. Reset it after each test because simulate cannot reach a second JavaScript process.

Remove stale Node test sockets clear-node-folders

import { clearNodeFolder } from 'broadcast-channel'

beforeAll(async () => {
  const cleared = await clearNodeFolder()
  console.log({ cleared })
})

clearNodeFolder() reports true under Node and false in browsers. Run it before an isolated suite, not while other processes use those channels.

Give one tab the shared connection elect-tab-leader

import { BroadcastChannel, createLeaderElection } from 'broadcast-channel'

const channel = new BroadcastChannel('server-connection')
const elector = createLeaderElection(channel)

await elector.awaitLeadership()
const connection = connectToServer()

awaitLeadership() starts the election lazily. The work after it should be idempotent because the README allows rare duplicate leaders.

Wait longer for throttled peers tune-election

const elector = createLeaderElection(channel, {
  responseTime: 1000,
  fallbackInterval: 2000,
})

await elector.awaitLeadership()

A 1,000 ms response window can reduce duplicate elections on slow tabs, while the 2,000 ms fallback interval also delays failover.

Stop work after duplicate leadership handle-duplicate-leaders

const elector = createLeaderElection(channel)

elector.onduplicate = async () => {
  stopExclusiveWork()
  await elector.die()
  reportDuplicateLeader()
}

await elector.awaitLeadership()
startIdempotentWork()

onduplicate detects a conflict after it happens. Keep irreversible operations behind a server-side lock or idempotency check.

Retire an elector once release-leadership

if (elector.isLeader) {
  await stopLeaderOnlyResources()
}

await elector.die()
await channel.close()

Version 7.4.0 makes repeated die() calls share one shutdown promise. An elector that has died cannot compete again.

Alternatives

PackageRegistryPick it when
@vueuse/corenpmChoose its useBroadcastChannel helper when the application is Vue-only and native browser support is sufficient
redux-state-syncnpmChoose it when the concrete requirement is copying selected Redux actions between tabs
redisnpmChoose Redis pub/sub when Node publishers span machines or exceed local filesystem IPC
wsnpmChoose WebSockets when a server must connect browsers across origins or devices

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.