Web Bluetooth
Bluetooth Low Energy devices from a web page: heart-rate monitors, sensors, toys, lights…
requestDevice() opens a browser chooser; once chosen, the page connects to the device’s GATT server, reads and writes characteristics and receives notifications. Standard services (battery, heart rate) have names; custom ones, a UUID.
Chrome and Edge on Windows, macOS, ChromeOS and Android; on Linux you have to enable experimental features. Firefox and Safari do not implement it and have taken negative positions. getDevices() and watchAdvertisements() are still not enabled by default.
What it is for
- Setting up and updating IoT devices
- Sport and health: heart-rate monitors, scales, smart trainers
- Education and robotics (micro:bit, kits)
Demo
Two standard Bluetooth LE services: battery level (watches, earbuds, controllers, mice…) and heart rate from a monitor or chest strap. The browser lets you pick the device; the page sees nothing else.
—
Browser support
Limited availability| Feature | Desktop | Mobile | |||||
|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS | |
| navigator.bluetooth | 561Chrome: 56 | 792Edge: 79 | —Firefox: No | —Safari: No | 56Chrome: 56 | —Firefox: No | —Safari: No |
- 1macOS and ChromeOS since 56; Windows 10 since 70. Linux needs experimental features enabled.
- 2Behind a flag on Linux.
Compatibility generated on every deploy from MDN browser-compat-data 8.1.3 (24 Sep 2026) · web-features 3.40.0 · checked by hand: 1 Oct 2026
Minimal code
<!doctype html>
<meta charset="utf-8">
<title>Web Bluetooth · ejemplo mínimo</title>
<!-- Lee el nivel de batería de un dispositivo Bluetooth LE que publique el servicio estándar «battery_service».
Chrome y Edge (escritorio y Android). HTTPS o localhost. -->
<button id="connect">Leer batería</button>
<pre id="out"></pre>
<script>
const out = document.querySelector('#out');
document.querySelector('#connect').onclick = async () => {
if (!('bluetooth' in navigator)) return (out.textContent = 'Web Bluetooth no está disponible en este navegador.');
try {
// requestDevice debe ser lo primero que se espera dentro del clic
const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] });
const server = await device.gatt.connect();
const service = await server.getPrimaryService('battery_service');
const level = await service.getCharacteristic('battery_level');
const show = (value) => (out.textContent = `${device.name ?? 'Dispositivo'}: ${value.getUint8(0)} %`);
show(await level.readValue());
// Si el dispositivo lo permite, recibe los cambios
if (level.properties.notify) {
level.addEventListener('characteristicvaluechanged', (e) => show(e.target.value));
await level.startNotifications();
}
device.addEventListener('gattserverdisconnected', () => (out.textContent += '\nDesconectado'));
} catch (err) {
out.textContent = err.name === 'NotFoundError' ? 'No se eligió ningún dispositivo.' : err.message;
}
};
</script><!doctype html>
<meta charset="utf-8">
<title>Web Bluetooth · ejemplo mínimo</title>
<!-- Lee el nivel de batería de un dispositivo Bluetooth LE que publique el servicio estándar «battery_service».
Chrome y Edge (escritorio y Android). HTTPS o localhost. -->
<button id="connect">Leer batería</button>
<pre id="out"></pre>
<script>
const out = document.querySelector('#out');
document.querySelector('#connect').onclick = async () => {
if (!('bluetooth' in navigator)) return (out.textContent = 'Web Bluetooth no está disponible en este navegador.');
try {
// requestDevice debe ser lo primero que se espera dentro del clic
const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] });
const server = await device.gatt.connect();
const service = await server.getPrimaryService('battery_service');
const level = await service.getCharacteristic('battery_level');
const show = (value) => (out.textContent = `${device.name ?? 'Dispositivo'}: ${value.getUint8(0)} %`);
show(await level.readValue());
// Si el dispositivo lo permite, recibe los cambios
if (level.properties.notify) {
level.addEventListener('characteristicvaluechanged', (e) => show(e.target.value));
await level.startNotifications();
}
device.addEventListener('gattserverdisconnected', () => (out.textContent += '\nDesconectado'));
} catch (err) {
out.textContent = err.name === 'NotFoundError' ? 'No se eligió ningún dispositivo.' : err.message;
}
};
</script>What’s new
Keep in mind
- requestDevice() must be the first thing awaited in the click handler, or the user gesture is lost.
- Declare in optionalServices any service you will use that is not in the filters.
- There is a blocklist of services and characteristics (GATT blocklist). In iframes, allow="bluetooth".
Further reading
Sources
- MDN browser-compat-data 8.1.324 Sep 2026
- Blink · watchAdvertisements no se activará10 Oct 2023
- Mozilla standards-positions #95accessed 1 Oct 2026
- WebKit standards-positions #570accessed 1 Oct 2026