Web APIs, todayLab · Alex Harto

Web Bluetooth

Bluetooth Low Energy devices from a web page: heart-rate monitors, sensors, toys, lights…

In your browser: checking…Reviewed 1 Oct 2026

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
FeatureDesktopMobile
ChromeEdgeFirefoxSafariChrome AndroidFirefox AndroidSafari iOS
navigator.bluetooth561Chrome: 56 792Edge: 79 —Firefox: No—Safari: No56Chrome: 56 —Firefox: No—Safari: No
  1. 1macOS and ChromeOS since 56; Windows 10 since 70. Linux needs experimental features enabled.
  2. 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>

What’s new

  1. WHATWG opens a hardware workstream, for now covering Web Serial only; Web Bluetooth is on hold.

Keep in mind

  1. requestDevice() must be the first thing awaited in the click handler, or the user gesture is lost.
  2. Declare in optionalServices any service you will use that is not in the filters.
  3. There is a blocklist of services and characteristics (GATT blocklist). In iframes, allow="bluetooth".

Further reading

Sources