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:

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.');
  }
}