How to Use the Web Bluetooth API in JavaScript
The Web Bluetooth API enables web applications to securely discover, connect, and interact with nearby Bluetooth Low Energy (BLE) peripherals directly from the browser. By leveraging standard Generic Attribute Profile (GATT) communication, JavaScript can read sensor data, transmit control commands, and receive real-time notifications from hardware devices without requiring native platform-specific applications. This guide explains how the API works and details the step-by-step process of establishing a connection and transferring data.
Prerequisites and Security Requirements
To protect user privacy and device security, browsers enforce strict requirements before JavaScript can interact with Bluetooth hardware:
- HTTPS Required: The API is only available in secure
contexts (
https://orlocalhost). - User Gesture Required: Scanning for devices must be triggered by a direct user interaction, such as clicking a button.
- Explicit User Consent: The browser displays a native device picker, preventing scripts from silently discovering or connecting to nearby hardware.
The BLE Communication Hierarchy
Communication with a peripheral follows the BLE GATT hierarchy: 1. Device: The physical Bluetooth peripheral. 2. GATT Server: The host on the device that contains services. 3. Service: A collection of related data points and functions (identified by UUID). 4. Characteristic: A specific data container within a service that holds a value and defines capabilities (Read, Write, Notify).
Step-by-Step Connection Process
1. Requesting the Device
Use navigator.bluetooth.requestDevice() within an event
listener to open the browser’s device chooser. You must provide filters
specifying service UUIDs or device name prefixes, or set
acceptAllDevices: true alongside
optionalServices.
document.getElementById('connect-btn').addEventListener('click', async () => {
try {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['battery_service'] }],
// Or: acceptAllDevices: true, optionalServices: ['battery_service']
});
console.log(`Connected to: ${device.name}`);
await connectToDevice(device);
} catch (error) {
console.error('User cancelled or connection failed:', error);
}
});2. Connecting to the GATT Server
Once the user selects a device, access the gatt property
and call connect() to establish a connection.
async function connectToDevice(device) {
const server = await device.gatt.connect();
console.log('GATT Server connected');
return server;
}3. Accessing Services and Characteristics
With an active GATT connection, query the target service, then retrieve the specific characteristic you intend to read or modify.
// Access the standard Battery Service
const service = await server.getPrimaryService('battery_service');
// Access the Battery Level Characteristic
const characteristic = await service.getCharacteristic('battery_level');Interacting with Characteristic Data
Reading Data
To retrieve data, call readValue(), which returns a
DataView object representing raw binary data.
const value = await characteristic.readValue();
const batteryLevel = value.getUint8(0);
console.log(`Battery level: ${batteryLevel}%`);Writing Data
To send data to the peripheral, pass an ArrayBuffer or a
typed array view (such as Uint8Array) to
writeValue() or writeValueWithResponse().
// Example: Sending a command byte to a custom characteristic
const command = new Uint8Array([0x01, 0xFF]);
await characteristic.writeValueWithResponse(command);
console.log('Command successfully sent');Subscribing to Real-Time Notifications
For peripherals that emit continuous data (like heart rate monitors or accelerometers), subscribe to value changes rather than polling the device.
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
// Parse and handle the incoming binary stream
const heartRate = value.getUint8(1);
console.log(`Heart Rate: ${heartRate} BPM`);
});Disconnecting and Handling Disconnections
Monitor the device’s connection status by listening to the
gattserverdisconnected event, and cleanly disconnect when
communication is no longer needed.
device.addEventListener('gattserverdisconnected', () => {
console.log('Device disconnected unexpectedly.');
});
// Manual disconnect
function disconnectDevice(device) {
if (device.gatt.connected) {
device.gatt.disconnect();
console.log('Device disconnected successfully.');
}
}