← How-Tos
electronics 1 hr ago ◯ 5 min read

Web Bluetooth for ESP32: Building Live BLE Dashboards Directly in the Browser

esp32web bluetoothblegattbrowserjavascriptchromewireless dashboardno app

Most ESP32 Bluetooth projects follow the same pattern: build a phone app (or use a generic BLE scanner app that half-works), pair it with the board, and hope the app survives the next phone OS update. There's a less-known alternative that skips the app entirely — Web Bluetooth, a browser API that lets a normal web page scan for, connect to, and exchange data with a BLE peripheral directly, no app store, no installation, and no native code. This guide covers building a browser-based dashboard for an ESP32 BLE peripheral using nothing but HTML, JavaScript, and the Web Bluetooth API.

What Web Bluetooth Actually Is

Web Bluetooth is a JavaScript API (`navigator.bluetooth`) supported in Chrome, Edge, and other Chromium-based browsers on desktop and Android, that gives a web page permission to talk to a specific BLE device the user explicitly selects from a browser-native device picker. It's deliberately restrictive by design — a page can't silently scan for and connect to devices in the background, the user has to click a button that triggers the picker, and the page only gets access to the exact device and services the user approved. This is a genuinely different tool from the WebSerial/WebUSB approach covered in our WebSerial and WebUSB guide, which talks to the board over a wired USB connection; Web Bluetooth is wireless, works from across a room, and doesn't require the board to be tethered to the same machine running the browser tab at all.

Setting Up the ESP32 Side: GATT Services and Characteristics

The ESP32 side of this is standard BLE peripheral code using the ESP32 BLE Arduino library (or NimBLE-Arduino for a smaller memory footprint). You define a GATT service with a unique UUID, add one or more characteristics under it (each with its own UUID) for the data you want to expose — a sensor reading, a status flag, a command input — and set each characteristic's properties (Read, Write, Notify) based on whether the browser will read it on demand, subscribe to live updates, or send commands to it. A minimal temperature-dashboard peripheral needs one service and one Notify characteristic; a two-way control dashboard (read sensor data, write relay commands) needs at least two characteristics with different property flags.

The Browser Side: Connecting and Subscribing

On the page side, the flow is: call `navigator.bluetooth.requestDevice()` with a filter matching your service UUID (this triggers the native device picker), call `.connect()` on the returned device's GATT server, get the specific service and characteristic by UUID, then either call `.readValue()` for a one-time read or `.startNotifications()` plus an event listener for live push updates as the ESP32's sensor value changes. The entire round trip — scan, pick, connect, subscribe — can be under 30 lines of vanilla JavaScript, no build tooling or frameworks required, which is what makes this approach appealing for quick project dashboards you want to just open and use.

StepESP32 SideBrowser Side AdvertiseStart BLE server, advertise service UUID— Discover—navigator.bluetooth.requestDevice({filters:[{services:[UUID]}]}) ConnectAccept incoming GATT connectiondevice.gatt.connect() Read dataUpdate characteristic valuecharacteristic.readValue() Live updatesCall notify() on value changecharacteristic.startNotifications() + event listener Send commandHandle write callback on characteristiccharacteristic.writeValue(buffer)

A Practical Example: Live Sensor Dashboard

A common first project is a browser page that shows a live-updating chart of a sensor value (temperature, distance, light level) coming off an ESP32 with no app install. The ESP32 reads its sensor on a timer, packs the value into a byte buffer, and calls `notify()` on the characteristic whenever the value changes meaningfully. The browser page subscribes once via `startNotifications()`, and every notification fires a `characteristicvaluechanged` event the page uses to update a chart or numeric readout in real time. Because this all happens directly in the browser's JavaScript engine, you can host the page as a single static HTML file — even opened directly from the local filesystem in some browsers, or served from a tiny local web server — with zero backend and zero app store review process standing between you and a working dashboard.

Limitations Worth Knowing Before You Commit to This Approach

Web Bluetooth is not universally supported: Safari (desktop and iOS) does not implement it at all, and Firefox has left it behind an experimental flag rather than shipping it by default, so this approach is realistically a Chrome/Chromium/Edge-only solution across desktop and Android. It also requires a user-initiated gesture (a button click) to open the device picker — you cannot auto-connect on page load, which is a deliberate privacy protection but does mean every session starts with one manual click. And unlike a proper native app, the page has no access to BLE beyond what Web Bluetooth exposes; background scanning, iBeacon-style passive detection, and OS-level Bluetooth settings are out of scope.

When to Use This vs a Real App

Web Bluetooth is the right call for personal dashboards, quick prototypes, one-off project demos, and anything where you (or a small, tech-comfortable audience) will use Chrome or Edge and don't need iOS support. If you're building something for wider distribution, need iOS compatibility, or need background/passive BLE behavior a page can't provide, a real native or cross-platform app (or a simpler MQTT/WiFi-based approach instead of BLE) is still the better tool. But for the very common case of "I want to see live data from my ESP32 project on a screen without writing and maintaining a phone app," Web Bluetooth removes an entire layer of complexity that most hobby projects don't actually need.