Web Bluetooth API: BLE from the Browser
Connecting to BLE devices using JavaScript and Chrome
Web Bluetooth API: BLE from the Browser
The Web Bluetooth API enables browser-based applications to communicate directly with BLE peripherals — no native app, no driver, no USB cable. Supported in Chrome, Edge, and Chromium-based browsers on desktop and Android, it exposes a subset of the GATT client role through an async JavaScript API.
Browser Support Matrix
| Browser | Desktop | Android | iOS / Safari |
|---|---|---|---|
| Chrome 56+ | Yes | Yes | No (WebKit policy) |
| Edge (Chromium) | Yes | Yes | No |
| Firefox | No (intentional) | No | No |
| Samsung Internet | Yes | Yes | No |
Web Bluetooth requires a secure context (HTTPS or localhost) and a user gesture to trigger device discovery. Chrome on Linux requires experimental flags; macOS and Windows work out of the box.
Device Discovery: requestDevice()
const device = await navigator.bluetooth.requestDevice({
filters: [
{ services: ['heart_rate'] }, // GATT service UUID filter
{ name: 'MyBLESensor' }, // exact device name
{ namePrefix: 'HR-' }, // name prefix
],
optionalServices: ['battery_service'], // services to access post-connect
});
The browser presents a native picker dialog. Your code never enumerates all nearby devices — by design, for privacy. Filters accept standard ATT">gatt-service/" class="glossary-term-link" data-term="GATT service" data-definition="Collection of related BLE characteristics." data-category="GATT & ATT">GATT service UUIDs (string aliases like 'heart_rate' or full 128-bit hex strings) or device names.
Connecting and Accessing GATT
const server = await device.gatt.connect();
const service = await server.getPrimaryService('heart_rate');
const char = await service.getCharacteristic('heart_rate_measurement');
// One-shot read
const value = await char.readValue();
const bpm = value.getUint8(1); // heart rate value per HRS spec
// Subscribe to notifications
await char.startNotifications();
char.addEventListener('characteristicvaluechanged', (event) => {
const data = event.target.value; // DataView
console.log('BPM:', data.getUint8(1));
});
The GATT hierarchy — server → service → characteristic → descriptor — mirrors the native BLE stack exactly. MTU negotiation is handled by the browser; you get up to 512 bytes per characteristic write in practice.
Writing Data and Handling Descriptors
// Write without response (faster, no ACK)
await char.writeValueWithoutResponse(new Uint8Array([0x01, 0x02]));
// Write with response (reliable)
await char.writeValueWithResponse(buffer);
// Enable notifications via CCCD descriptor
const cccd = await char.getDescriptor('client_characteristic_configuration');
await cccd.writeValue(new Uint8Array([0x01, 0x00])); // notify ON
The CCCD (Client Characteristic Configuration Descriptor) enables notifications and indications. Web Bluetooth's startNotifications() writes the CCCD for you, but you can also manipulate descriptors directly.
Reconnection and Disconnect Handling
device.addEventListener('gattserverdisconnected', async () => {
console.log('Disconnected, reconnecting...');
await exponentialBackoff(3, 2, () => device.gatt.connect());
});
async function exponentialBackoff(max, delay, fn) {
for (let i = 0; i < max; i++) {
try { return await fn(); }
catch(err) { if (i === max - 1) throw err; }
await new Promise(r => setTimeout(r, delay ** i * 1000));
}
}
Scanning Without Connection (watchAdvertisements)
await device.watchAdvertisements();
device.addEventListener('advertisementreceived', (event) => {
console.log('RSSI:', event.rssi);
console.log('Service data:', event.serviceData);
});
watchAdvertisements() is a newer API (Chrome 78+) for passive scanning of cached device advertisements without a GATT connection — useful for RSSI-based proximity or beacon data extraction.
Building a Complete BLE Web App
A minimal heart rate monitor page:
<button id="connect">Connect</button>
<p id="bpm">—</p>
<script>
document.getElementById('connect').onclick = async () => {
const dev = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }]
});
const srv = await dev.gatt.connect();
const svc = await srv.getPrimaryService('heart_rate');
const chr = await svc.getCharacteristic('heart_rate_measurement');
await chr.startNotifications();
chr.addEventListener('characteristicvaluechanged', e => {
document.getElementById('bpm').textContent = e.target.value.getUint8(1) + ' bpm';
});
};
</script>
Limitations
- No background scanning (tab must be visible on some browsers)
- No advertising — browser can only act as a GATT client
- No raw HCI or L2CAP access
- iOS Safari exclusion affects a large mobile segment — consider a React Native or Flutter fallback for iOS
Use the GATT Browser to explore your peripheral's service tree before writing Web Bluetooth code.
자주 묻는 질문
Yes, our guides range from beginner introductions to advanced topics. Each guide indicates its difficulty level and prerequisites so you can find the right starting point.