Compatibility
What works where, and why. An honest capability report across browsers, adapters, and hardware.
Web Bluetooth is not a universal API. It ships in Chromium; it does not ship in Firefox or Safari, and neither vendor is likely to change that soon. This page states plainly what runs where, why the gaps exist, and how you can bridge them yourself if you need to.
The three adapters
agnostic-web-ble ships two adapters and defines a contract for a third:
native— wrapsnavigator.bluetooth. Runs anywhere the browser exposes the Web Bluetooth API. In practice: Chromium desktop, Chromium Android, ChromeOS. ReportsrequiresUserGesture: truebecauserequestDevice()must be called from a user activation.mock— in-memory adapter for tests and stories. ReportsrequiresUserGesture: false. Never touches hardware. Deterministic, seeded, drive-controllable viaMockDeviceHandle.custom— anything satisfying theBluetoothAdapterinterface. This is how you reach Firefox and Safari: build an adapter over a transport your browser does support (WebSocket to a local helper, native messaging, whatever) and register it alongsidenative. See custom adapters.
Selection is deterministic — first available wins. Pass adapters in priority order:
const bt = createBluetooth({
adapters: [nativeWebBluetoothAdapter(), myBridgeAdapter()],
});On Chromium the native adapter's isAvailable() returns true and it wins. On Firefox and Safari it returns false and the next adapter is tried. If none are available you get BluetoothError { code: "UNAVAILABLE" } naming every adapter that was tried along with its reason.
Matrix
| Runtime | Adapter | Request | Connect | Read | Write | Notify |
|---|---|---|---|---|---|---|
| Chrome / Edge / Opera (desktop) | native | Implemented | Implemented | Implemented | Implemented | Implemented |
| Requires HTTPS or localhost. | ||||||
| Chrome (Android) | native | Implemented | Implemented | Implemented | Implemented | Implemented |
| Since Android 6. | ||||||
| ChromeOS | native | Implemented | Implemented | Implemented | Implemented | Implemented |
| Firefox (all platforms) | native | Not supported | Not supported | Not supported | Not supported | Not supported |
| navigator.bluetooth is undefined. No pref, no flag. | ||||||
| Safari (macOS / iOS) | native | Not supported | Not supported | Not supported | Not supported | Not supported |
| Same as Firefox. Chrome on iOS inherits WebKit and cannot escape this. | ||||||
| Any browser | custom (bridge) | You choose | You choose | You choose | You choose | You choose |
| Your transport, your capability report. Be honest to what you built. | ||||||
| Node / Vitest / Playwright | mock | Implemented | Implemented | Implemented | Implemented | Implemented |
| Zero hardware. requiresUserGesture: false. | ||||||
Capability space
The native adapter is a thin wrapper over navigator.bluetooth. If that global does not exist in a given browser, no version of this library will make it exist. That is not a limitation of agnostic-web-ble — it is a property of the browser.
Why not Firefox and Safari?
Mozilla — position: harmful. Mozilla's official standards position on Web Bluetooth is harmful. Their concerns are the permission model, the surface area of ambient device access, and the difficulty of meaningful user consent for opaque GATT services. See mozilla/standards-positions #95. The Firefox tracking bug, Bugzilla 674737, has sat without implementation work for over a decade. There is no hidden pref, no nightly flag, no partial implementation — about:config has no dom.webbluetooth.enabled; the code does not exist in Gecko.
Apple — no signal. WebKit's tracking bug for Web Bluetooth, WebKit bug 101034, has been open since 2013 with no implementation work and no public position paper. iOS is stricter: every browser on iOS is required to use WebKit, so Chrome and Firefox on iOS inherit Safari's answer.
The sibling APIs don't help either. WebSerial, WebHID, WebUSB, and Web NFC follow the same shape as Web Bluetooth: Chromium ships them, Mozilla marks them harmful, WebKit does not implement. You can't polyfill Web Bluetooth on top of them in Firefox or Safari because Firefox and Safari don't ship them either. The WebBluetoothCG implementation status tracks this.
State it plainly: on Firefox and Safari, navigator.bluetooth is undefined, and it will remain undefined. Any library that claims otherwise is either lying, requires a browser extension, or is proxying through a native host you install separately.
Extending to Firefox and Safari yourself
If you control both ends — the web page and the machine it runs on — you can bridge to Firefox and Safari today. Write a small host process that speaks to the OS Bluetooth stack (BlueZ on Linux, CoreBluetooth on macOS, WinRT on Windows) and exposes a WebSocket. Then implement a BluetoothAdapter that satisfies the same contract as native but talks to your bridge instead of navigator.bluetooth. Pass it to createBluetooth({ adapters: [native, bridge] }) and the library will pick whichever is available at runtime — your code above the adapter layer does not change.
agnostic-web-ble does not ship a bridge. Bridges are deployment-specific: authentication, transport, host lifecycle, and OS integration are choices you have to make, and shipping a default would push those choices onto you. The adapter contract is small enough that a working bridge is a weekend, not a quarter.
See custom adapters for the full contract and a reference bridge sketch.