Web Bluetooth API: connecting to Bluetooth Low Energy devices
In one line: The Web Bluetooth API lets a web app scan for and connect to nearby Bluetooth Low Energy (BLE) devices — heart-rate monitors, smart locks, sensors, game controllers — through a browser-mediated device picker, without a native app. It is a WICG specification currently shipping in Chromium-based browsers.
Spec status
Section titled “Spec status”Web Bluetooth is defined by a WICG (Web Incubator Community Group) specification, not a finalized W3C standard. It is available in Chrome and Edge (desktop and Android) but not in Firefox or Safari. Always feature-detect and provide a fallback or clear messaging for unsupported browsers.
The device-picker flow
Section titled “The device-picker flow”async function connectHeartRateMonitor() { // 1. Request device — shows the browser's device-chooser UI const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }], // optionalServices: ['battery_service'], });
// 2. Connect to the GATT server on the device const server = await device.gatt.connect();
// 3. Get the primary service const service = await server.getPrimaryService('heart_rate');
// 4. Get the characteristic const characteristic = await service.getCharacteristic('heart_rate_measurement');
// 5. Subscribe to notifications characteristic.addEventListener('characteristicvaluechanged', (event) => { const value = event.target.value; // DataView console.log('Heart rate:', value.getUint8(1), 'bpm'); }); await characteristic.startNotifications();}User-gesture requirement
Section titled “User-gesture requirement”navigator.bluetooth.requestDevice() must be called from a user activation (a
click, tap, or similar transient event). Calling it programmatically without a gesture
throws a SecurityError. The picker cannot be pre-answered or bypassed.
Secure context (HTTPS)
Section titled “Secure context (HTTPS)”The Web Bluetooth API is only available in secure contexts — HTTPS or localhost.
On plain HTTP, navigator.bluetooth is undefined.
Permission model
Section titled “Permission model”Web Bluetooth does not use the standard Permissions API permission names. Access is
device-scoped: the user picks a specific device from the browser’s chooser, and only
that device is accessible to the origin. There is no “grant Bluetooth access broadly”
prompt — each requestDevice() call targets specific services via filters or
acceptAllDevices.
- Granted device access persists across page loads for the same origin (the user does not have to re-pick on every visit), but can be revoked in browser settings.
device.gatt.connect()can reconnect to a previously chosen device without showing the picker again — call it from a user gesture if the device is not already connected.
Filters and acceptAllDevices
Section titled “Filters and acceptAllDevices”filters: an array of filter objects. Each can includeservices(GATT service UUIDs),name,namePrefix, ormanufacturerData. Only devices matching a filter appear in the picker.acceptAllDevices: true: shows all nearby BLE devices. Use only for development or when the device type cannot be known in advance; it requires listing every service you intend to use inoptionalServices.
GATT services and characteristics
Section titled “GATT services and characteristics”BLE communication is structured as a hierarchy:
- Services group related functionality (e.g.,
heart_rate,battery_service). - Characteristics are individual data points within a service (e.g.,
heart_rate_measurement). - Descriptors provide metadata about a characteristic.
You can use the standard 16-bit Bluetooth SIG UUIDs (short names like 'heart_rate'
resolve to their full UUID automatically) or full 128-bit custom UUIDs for vendor
proprietary services.
Browser & ecosystem support
Section titled “Browser & ecosystem support”See /compatibility/ for current per-browser data.
Decision framework
Section titled “Decision framework”| Decision question | Recommended action | Rationale |
|---|---|---|
| Targeting Chrome/Edge users? | Web Bluetooth is available on Chrome desktop, Chrome Android, and Edge. | Chromium-only at this time; Firefox and Safari do not implement it. |
| Need to work on Firefox/Safari too? | Show a clear “not supported in this browser” message; consider a native companion app. | No polyfill can replicate BLE access; set expectations early. |
| Connecting to a known device type (e.g., HR monitor)? | Use specific filters: [{ services: ['heart_rate'] }]. |
Narrows the picker to compatible devices; avoids showing unrelated BLE devices. |
| Need to reconnect without prompting the picker again? | Store device and call device.gatt.connect() in a click handler. |
The origin retains access to previously chosen devices. |
Practical checklist
Section titled “Practical checklist”- Feature-detect:
if (!navigator.bluetooth)before calling any Web Bluetooth method. - Serve the page over HTTPS — the API is unavailable on plain HTTP.
- Call
requestDevice()only from a user-gesture handler. - Use specific
filtersrather thanacceptAllDevicesin production. - List all services you need in
filtersoroptionalServices— the browser blocks access to unlisted services. - Handle disconnection events (
device.addEventListener('gattserverdisconnected', ...)) and implement reconnection logic. - Communicate clearly that this feature requires a Chromium-based browser.