This example gives the WebBluetooth backend to a web page as a small JavaScript API. The page opens the device picker of the browser, reads the Device Information service of the device that you select, and can subscribe to a characteristic that sends notifications.
The WASM module only holds the binding. The page in html/index.html does all of the display work.
- TinyGo, or the standard Go toolchain.
- A browser with WebBluetooth support, such as Chrome or Edge. See the browser compatibility table.
WebBluetooth is only available in a secure context. A page on localhost counts as secure, so you do not need HTTPS during development.
The page needs two build products in the html directory: the module wasm.wasm, and the JavaScript support file wasm_exec.js. TinyGo and the standard Go toolchain each have their own support file. Always take both products from the same toolchain.
Run all commands from the root of the repository.
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" ./examples/webbluetooth/html/
tinygo build -o ./examples/webbluetooth/html/wasm.wasm -target wasm ./examples/webbluetooth/cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./examples/webbluetooth/html/
GOOS=js GOARCH=wasm go build -o ./examples/webbluetooth/html/wasm.wasm ./examples/webbluetooth/Go versions before 1.24 keep the support file in misc/wasm/wasm_exec.js.
The TinyGo commands also work in PowerShell. The standard Go toolchain needs the two variables in the environment:
$env:GOOS = "js"; $env:GOARCH = "wasm"
go build -o ./examples/webbluetooth/html/wasm.wasm ./examples/webbluetooth/go run ./examples/webbluetooth/server/Open http://localhost:8080 and click Connect. The browser shows the device picker. Select a device to see the result in the page.
Both wasm.wasm and wasm_exec.js are build output, so git ignores them.
Run one of these examples on a second machine, because a Bluetooth adapter cannot connect to itself. Linux and Windows can be a peripheral. macOS cannot.
go run ./examples/device-informationgives the values that the Device Information panel shows.go run ./examples/batterygives the Battery Level notifications that the Notifications panel uses by default.
The module sets globalThis.ble, then calls globalThis.onBleReady if the page gives that function. Define onBleReady before you start the module.
Every function returns a Promise. A function that JavaScript calls must not block, because it holds the event loop until it returns, and each call into the Bluetooth package waits for a promise.
| Function | Result |
|---|---|
ble.enable() |
Prepares the adapter. |
ble.requestDevice(serviceUUIDs) |
Opens the device picker. Gives {id, name} for the device that the user selects, or null if the user closes the picker. |
ble.connect(id) |
Connects to the device with that id. |
ble.disconnect() |
Closes the connection. |
ble.read(service, characteristic) |
Gives the value as a Uint8Array. |
ble.readString(service, characteristic) |
Gives the value as a string. |
ble.subscribe(service, characteristic, callback) |
Starts notifications. Calls the callback with a Uint8Array for each new value. |
ble.unsubscribe(service, characteristic) |
Stops notifications. |
ble.onConnectionChange(callback) |
Calls the callback with a boolean on each connection and disconnection. Call it before connect. |
Give every service that the page uses to requestDevice. The browser refuses access to a service that the page did not ask for.
globalThis.onBleReady = async () => {
await ble.enable();
const device = await ble.requestDevice(["0000180a-0000-1000-8000-00805f9b34fb"]);
if (!device) return;
await ble.connect(device.id);
console.log(await ble.readString(
"0000180a-0000-1000-8000-00805f9b34fb",
"00002a29-0000-1000-8000-00805f9b34fb"));
};- The browser opens the device picker only after a recent user action, such as a click. Call
ble.requestDevicesoon after that action, and do no slow work first. - The browser gives an opaque device ID instead of a MAC address. The
Addresstype holds this ID. - You must call
ScanbeforeConnect. The browser object for the device does not survive a page reload. - You must list every service that you want to use in
Adapter.RequestedServicesbefore you callScan. The browser refuses access to a service that is not in the list. - The browser does not report the RSSI of the selected device.
- The browser does not give the negotiated MTU.
GetMTUreturns 512. - WebBluetooth has no peripheral role, so advertisement and local services are not available.