loading
Warning:
Chromium only, over HTTPS Web Bluetooth ships in Chromium-based browsers on desktop and Android. Firefox and Safari do not implement it, and on Linux the browser also needs BlueZ. Check IsSupported and GetAvailability before offering the feature, and keep every call inside a try/catch.
Note:
The grant is per serviceRequestDevice must run inside a user gesture, and the permission it produces covers only the services the request named. A device picked with no OptionalServices can be connected to and nothing else - every getPrimaryService for an unnamed service is a SecurityError. The defaults below ask for battery_service and device_information, which most peripherals expose.

Support and availability

IsSupported / GetAvailability

IsSupported reports whether navigator.bluetooth exists at all; GetAvailability reports whether this machine has a radio the browser can use, which is the difference between 'your browser cannot' and 'your Bluetooth is off'.

C#
@inject Bit.Butil.Bluetooth bluetooth

var supported = await bluetooth.IsSupported();
var available = await bluetooth.GetAvailability();
Live sample
support output
Results will appear here when you interact with the samples.

Pick a device

RequestDevice / GetDevices

RequestDevice opens the browser's chooser and returns a handle to the device the user picked. GetDevices returns the devices this origin was already granted, without a prompt.

C#
private BluetoothDevice? _device;

_device = await bluetooth.RequestDevice(new BluetoothRequestOptions
{
    AcceptAllDevices = true,
    OptionalServices = ["battery_service", "device_information"]
});

// or, without a prompt:
var granted = await bluetooth.GetDevices();
Live sample
Optional services (comma separated)
Name prefix filter (blank = accept all devices)
device output
Results will appear here when you interact with the samples.

Connect and browse GATT

Connect / Disconnect / IsConnected / GetServices / GetCharacteristics

Connect opens the GATT server. GetServices lists the primary services the grant covers, and GetCharacteristics lists one service's characteristics with the operations each of them supports.

Razor
@code {
    // The handle from RequestDevice above. A grant is per device, so this is the only way back to
    // one the user has already chosen.
    private BluetoothDevice? _device;

    private async Task Browse()
    {
        await _device!.Connect();

        foreach (var service in await _device.GetServices())
        {
            foreach (var c in await _device.GetCharacteristics(service.Uuid))
            {
                // c.Uuid, c.Read, c.Write, c.Notify, ...
            }
        }

        await _device.Disconnect();
    }
}
Live sample
no device picked yet
Service UUID / alias
gatt output
Results will appear here when you interact with the samples.

Read and write a characteristic

Read / Write

Read returns the characteristic's current bytes; Write sends bytes to it, either acknowledged or fire-and-forget. The battery level characteristic below is a single byte holding a percentage.

Razor
@code {
    private BluetoothDevice? _device;   // from RequestDevice, and connected

    private async Task ReadWrite()
    {
        var value = await _device!.Read("battery_service", "battery_level");
        var percent = value?[0];

        await _device.Write("0xFFE0", "0xFFE1", [0x01, 0x02], withResponse: true);
    }
}
Live sample
Service
Characteristic
Bytes to write (hex, space separated)
no device picked yet
value output
Results will appear here when you interact with the samples.

Notifications and disconnection

SubscribeValueChanged / SubscribeDisconnected

SubscribeValueChanged asks the device to push a characteristic's value as it changes - a heart-rate strap, a thermometer. SubscribeDisconnected fires when the link drops, which for a battery-powered peripheral happens on its own schedule.

Razor
@implements IAsyncDisposable

@code {
    private BluetoothDevice? _device;   // from RequestDevice, and connected
    private ButilSubscription? _values;
    private ButilSubscription? _dropped;

    private async Task Listen()
    {
        _values = await _device!.SubscribeValueChanged(
            "heart_rate", "heart_rate_measurement",
            bytes => Console.WriteLine(bytes.Length));

        _dropped = await _device.SubscribeDisconnected(
            () => Console.WriteLine("device went away"));
    }

    public async ValueTask DisposeAsync()
    {
        if (_values is not null) await _values.DisposeAsync();
        if (_dropped is not null) await _dropped.DisposeAsync();
    }
}
Live sample
no device picked yet
notification output
Results will appear here when you interact with the samples.

Revoke the grant

Forget

Forget drops this origin's permission for the device, so it stops appearing in GetDevices until the user picks it again. Not every Chromium version implements it - false means it was unavailable.

Razor
@code {
    private BluetoothDevice? _device;   // from RequestDevice

    // Drops the grant, so GetDevices stops returning it and the picker has to be shown again.
    private async Task Revoke()
    {
        var revoked = await _device!.Forget();
        _device = null;
    }
}
Live sample
forget output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes navigator.bluetooth. Returns default (false) during prerender/SSR instead of throwing.
GetAvailability
ValueTask<bool> GetAvailability()
True when the machine has a Bluetooth adapter the browser can use.
RequestDevice
ValueTask<BluetoothDevice?> RequestDevice(BluetoothRequestOptions? options = null)
Opens the device chooser and returns the picked device, or null when dismissed. Needs a user gesture.
GetDevices
ValueTask<BluetoothDevice[]> GetDevices()
The devices this origin has already been granted, without a prompt.
DisposeAsync
ValueTask DisposeAsync()
Disconnects every device this service handed out, detaches listeners and releases the JS callback reference.
InvokeBluetoothValueChanged
void InvokeBluetoothValueChanged(Guid id, byte[] value)
JSInvokable interop plumbing for characteristic notifications - not intended for app code.
InvokeBluetoothDisconnected
void InvokeBluetoothDisconnected(Guid id)
JSInvokable interop plumbing for GATT disconnection - not intended for app code.
BluetoothDevice.Info
BluetoothDeviceInfo Info { get; }
The device as it was when the handle was created: Id, DeviceId, Name, Connected.
BluetoothDevice.Connect
ValueTask<bool> Connect()
Opens the GATT connection.
BluetoothDevice.Disconnect
ValueTask Disconnect()
Closes the GATT connection without giving up the grant.
BluetoothDevice.IsConnected
ValueTask<bool> IsConnected()
True while the GATT server is connected.
BluetoothDevice.GetServices
ValueTask<BluetoothServiceInfo[]> GetServices()
The primary services the grant covers.
BluetoothDevice.GetCharacteristics
ValueTask<BluetoothCharacteristicInfo[]> GetCharacteristics(string serviceUuid)
One service's characteristics, each with the operations it supports.
BluetoothDevice.Read
ValueTask<byte[]?> Read(string serviceUuid, string characteristicUuid)
Reads a characteristic's current value.
BluetoothDevice.Write
ValueTask<bool> Write(string serviceUuid, string characteristicUuid, byte[] data, bool withResponse = true)
Writes a characteristic's value, acknowledged or not.
BluetoothDevice.SubscribeValueChanged
ValueTask<ButilSubscription> SubscribeValueChanged(string serviceUuid, string characteristicUuid, Action<byte[]> handler)
Subscribes to a characteristic's notifications. Dispose the subscription to stop them.
BluetoothDevice.SubscribeDisconnected
ValueTask<ButilSubscription> SubscribeDisconnected(Action handler)
Runs the handler when the device drops the GATT connection.
BluetoothDevice.Forget
ValueTask<bool> Forget()
Revokes this origin's permission for the device.
BluetoothDevice.DisposeAsync
ValueTask DisposeAsync()
Disconnects, detaches this device's listeners and releases the browser-side reference.
BluetoothRequestOptions
class BluetoothRequestOptions { BluetoothFilter[]? Filters; bool AcceptAllDevices; string[]? OptionalServices; }
What the chooser lists, and which services the grant covers.
BluetoothFilter
class BluetoothFilter { string[]? Services; string? Name; string? NamePrefix; }
One chooser filter. A device matches when it satisfies every property that is set.
BluetoothServiceInfo
class BluetoothServiceInfo { string Uuid; bool IsPrimary; }
A primary GATT service.
BluetoothCharacteristicInfo
class BluetoothCharacteristicInfo { string Uuid; string ServiceUuid; bool Broadcast; bool Read; bool WriteWithoutResponse; bool Write; bool Notify; bool Indicate; }
A characteristic and the operations the device declares for it.
An unhandled error has occurred. Reload 🗙