loading
Warning:
Chromium only, over HTTPS WebUSB ships in Chromium-based browsers. Firefox and Safari do not implement it. RequestDevice must run inside a user gesture.
Note:
The operating system gets first claim A device the OS already has a driver for - keyboards, mice, mass storage, most webcams - is blocked outright, and interfaces its driver holds cannot be claimed. WebUSB is for the devices no driver has taken: microcontroller boards, instruments, programmers.

Support check

IsSupported

True when the runtime exposes navigator.usb. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.

C#
@inject Bit.Butil.Usb usb

var supported = await usb.IsSupported();
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, optionally narrowed by vendor and product id. GetDevices returns what this origin was already granted, without a prompt.

C#
private UsbDevice? _device;

_device = await usb.RequestDevice();                       // every device the browser will show
_device = await usb.RequestDevice(new UsbDeviceFilter { VendorId = 0x2341 });

var granted = await usb.GetDevices();
Live sample
Vendor id (hex, blank for any)
Product id (hex, optional)
device output
Results will appear here when you interact with the samples.

Open, configure, claim

Open / SelectConfiguration / ClaimInterface / SelectAlternateInterface / ReleaseInterface / Close / IsOpened / GetInfo

The order is fixed: open, select a configuration, claim an interface, and only then transfer. Skipping a step fails with an InvalidStateError rather than a hint about which step was missed.

Razor
@code {
    // The handle from RequestDevice above - a grant is per device, and this is the way back to one
    // the user has already chosen.
    private UsbDevice? _device;

    private async Task Session()
    {
        await _device!.Open();
        await _device.SelectConfiguration(1);

        // Nothing can be transferred until the interface is claimed, and the OS may already hold it
        // for a driver of its own.
        await _device.ClaimInterface(0);

        // ... transfers ...

        await _device.ReleaseInterface(0);
        await _device.Close();
    }
}
Live sample
Configuration value
Interface number
Alternate setting
no device picked yet
setup output
Results will appear here when you interact with the samples.

Transfers

ControlTransferIn / ControlTransferOut / TransferIn / TransferOut / ClearHalt / Reset

Control transfers carry a setup packet and address the device itself; bulk and interrupt transfers address an endpoint number taken from the claimed interface's descriptor. A 'stall' status means the endpoint halted - ClearHalt is the way out.

Razor
@code {
    private UsbDevice? _device;   // opened, with its interface claimed

    private async Task Transfer()
    {
        var read = await _device!.TransferIn(endpointNumber: 1, length: 64);
        var text = read?.Data is null ? "" : System.Text.Encoding.UTF8.GetString(read.Data);

        await _device.TransferOut(endpointNumber: 2, "ping"u8.ToArray());

        await _device.ControlTransferOut(new UsbControlTransferParameters
        {
            RequestType = "vendor",
            Recipient = "device",
            Request = 0x01,
            Value = 0x0001,
            Index = 0
        });
    }
}
Live sample
IN endpoint
Read length
OUT endpoint
Bytes to send (hex, space separated)
no device picked yet
transfer output
Results will appear here when you interact with the samples.

Plug and unplug

SubscribeConnection

Watches devices appearing and disappearing. Only devices this origin already has permission for raise these - plugging in a stranger's device is not something the page is told about.

C#
await using var watch = await usb.SubscribeConnection(
    onConnected: device => Console.WriteLine($"+ {device.Info.ProductName}"),
    onDisconnected: device => Console.WriteLine($"- {device.Info.ProductName}"));
Live sample
connection output
Results will appear here when you interact with the samples.

Revoke the grant

Forget

Drops this origin's permission for the device, so it stops appearing in GetDevices until the user picks it again.

Razor
@code {
    private UsbDevice? _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.usb. Returns default (false) during prerender/SSR instead of throwing.
RequestDevice
ValueTask<UsbDevice?> RequestDevice(params UsbDeviceFilter[] filters)
Opens the device chooser and returns the picked device, or null when dismissed. Needs a user gesture.
GetDevices
ValueTask<UsbDevice[]> GetDevices()
The devices this origin has already been granted, without a prompt.
SubscribeConnection
ValueTask<ButilSubscription> SubscribeConnection(Action<UsbDevice>? onConnected = null, Action<UsbDevice>? onDisconnected = null)
Watches permitted devices being plugged in and unplugged.
DisposeAsync
ValueTask DisposeAsync()
Closes every device this service handed out, detaches listeners and releases the JS callback reference.
InvokeUsbConnected
void InvokeUsbConnected(Guid id, UsbDeviceInfo info)
JSInvokable interop plumbing for the connect event - not intended for app code.
InvokeUsbDisconnected
void InvokeUsbDisconnected(Guid id, UsbDeviceInfo info)
JSInvokable interop plumbing for the disconnect event - not intended for app code.
UsbDevice.Info
UsbDeviceInfo Info { get; }
The device as it was when the handle was created, including its descriptor tree.
UsbDevice.Open / Close / IsOpened
ValueTask<bool> Open() / ValueTask Close() / ValueTask<bool> IsOpened()
Opens, closes and reports the device's open state.
UsbDevice.GetInfo
ValueTask<UsbDeviceInfo?> GetInfo()
Re-reads the device's state; Info is only the snapshot from when the handle was created.
UsbDevice.SelectConfiguration
ValueTask<bool> SelectConfiguration(byte configurationValue)
Selects a configuration by its value.
UsbDevice.ClaimInterface / ReleaseInterface
ValueTask<bool> ClaimInterface(byte interfaceNumber) / ValueTask<bool> ReleaseInterface(byte interfaceNumber)
Takes and gives back exclusive use of an interface.
UsbDevice.SelectAlternateInterface
ValueTask<bool> SelectAlternateInterface(byte interfaceNumber, byte alternateSetting)
Switches a claimed interface to one of its alternate settings.
UsbDevice.ControlTransferIn / ControlTransferOut
ValueTask<UsbTransferResult?> ControlTransferIn(UsbControlTransferParameters parameters, ushort length) / ControlTransferOut(UsbControlTransferParameters parameters, byte[]? data = null)
Sends a control request, with an optional data stage in either direction.
UsbDevice.TransferIn / TransferOut
ValueTask<UsbTransferResult?> TransferIn(byte endpointNumber, uint length) / TransferOut(byte endpointNumber, byte[] data)
Reads from and writes to a bulk or interrupt endpoint.
UsbDevice.ClearHalt
ValueTask<bool> ClearHalt(string direction, byte endpointNumber)
Clears a halted endpoint - the only way out of a 'stall' status.
UsbDevice.Reset
ValueTask<bool> Reset()
Resets the device, abandoning every pending transfer.
UsbDevice.Forget
ValueTask<bool> Forget()
Revokes this origin's permission for the device.
UsbDevice.DisposeAsync
ValueTask DisposeAsync()
Closes the device and releases the browser-side reference.
UsbDeviceFilter
class UsbDeviceFilter { ushort? VendorId; ushort? ProductId; byte? ClassCode; byte? SubclassCode; byte? ProtocolCode; string? SerialNumber; }
One chooser filter.
UsbDeviceInfo
class UsbDeviceInfo { string Id; ushort VendorId; ushort ProductId; byte DeviceClass; byte DeviceSubclass; byte DeviceProtocol; string? ManufacturerName; string? ProductName; string? SerialNumber; bool Opened; byte? ConfigurationValue; UsbConfigurationInfo[] Configurations; }
A granted device and its descriptor tree.
UsbConfigurationInfo / UsbInterfaceInfo / UsbAlternateInterfaceInfo / UsbEndpointInfo
class UsbConfigurationInfo { ... UsbInterfaceInfo[] Interfaces; } etc.
The descriptor tree: configurations hold interfaces, interfaces hold alternate settings, settings hold endpoints.
UsbControlTransferParameters
class UsbControlTransferParameters { string RequestType; string Recipient; byte Request; ushort Value; ushort Index; }
The setup packet of a control transfer.
UsbTransferResult
class UsbTransferResult { string Status; uint BytesWritten; byte[]? Data; }
The outcome of a transfer: 'ok', 'stall' or 'babble', plus whichever of data/bytes-written applies.
An unhandled error has occurred. Reload 🗙