Skip to content

Capabilities · API

WebUSB API

Published

WebUSB gives a page a USB device that no operating-system class driver claims (development boards, programmers, custom instruments): navigator.usb.requestDevice() shows a chooser, and the resulting USBDevice is opened, configured, has an interface claimed, and then runs control, bulk, interrupt, or isochronous transfers. The specification is published by the WHATWG; the WICG URL redirects to it.

Chrome 61 and Edge 79 implement the API on desktop and Android, and Samsung Internet mirrors Chrome (BCD api.USB). Firefox has no implementation and Mozilla’s standards position on WebUSB is negative (mozilla/standards-positions#100); Safari has none; Android WebView exposes navigator.usb but every call fails (crbug.com/41441927). Chrome 70 also exposes the API in dedicated workers, and Chrome 118 in extension service workers (BCD api.USB.worker_support).

navigator.usb.requestDevice(options)
navigator.usb.getDevices()
device.open()
device.selectConfiguration(configurationValue)
device.claimInterface(interfaceNumber)
device.selectAlternateInterface(interfaceNumber, alternateSetting)
device.controlTransferIn(setup, length)
device.controlTransferOut(setup, data)
device.transferIn(endpointNumber, length)
device.transferOut(endpointNumber, data)
device.isochronousTransferIn(endpointNumber, packetLengths)
device.isochronousTransferOut(endpointNumber, data, packetLengths)
device.clearHalt(direction, endpointNumber)
device.releaseInterface(interfaceNumber)
device.reset()
device.close()
device.forget()

Every method returns a promise. requestDevice() resolves with one USBDevice and must run inside transient user activation; getDevices() resolves with the devices this origin was already granted and that are attached. Input transfers resolve with USBInTransferResult (data as a DataView, status of "ok", "stall", or "babble"); output transfers resolve with USBOutTransferResult (bytesWritten, status). navigator.usb fires connect and disconnect events for granted devices. All interfaces are [SecureContext].

requestDevice(options) takes a USBDeviceRequestOptions dictionary whose filters member is required (an empty array lists every device). Control transfers take a USBControlTransferParameters dictionary.

Dictionary Member Type Required Description
USBDeviceRequestOptions filters sequence<USBDeviceFilter> Yes A device matches when it satisfies every present member of any one filter.
USBDeviceRequestOptions exclusionFilters sequence<USBDeviceFilter> No Devices matching any of these are hidden from the chooser (Chrome 117).
USBDeviceFilter vendorId unsigned short No USB vendor ID.
USBDeviceFilter productId unsigned short No Product ID; valid only together with vendorId.
USBDeviceFilter classCode octet No Device or interface class.
USBDeviceFilter subclassCode octet No Requires classCode.
USBDeviceFilter protocolCode octet No Requires subclassCode.
USBDeviceFilter serialNumber DOMString No Exact serial-number string.
USBControlTransferParameters requestType USBRequestType Yes "standard", "class", or "vendor".
USBControlTransferParameters recipient USBRecipient Yes "device", "interface", "endpoint", or "other".
USBControlTransferParameters request octet Yes bRequest of the setup packet.
USBControlTransferParameters value unsigned short Yes wValue.
USBControlTransferParameters index unsigned short Yes wIndex; for "interface" and "endpoint" recipients the low byte must name a claimed interface or its endpoint.

A USBDevice exposes the descriptor tree read-only: configurations (array of USBConfiguration), each with interfaces (USBInterface), each with alternates (USBAlternateInterface) that list endpoints (USBEndpoint with endpointNumber, direction, type, packetSize). vendorId, productId, manufacturerName, productName, serialNumber, and opened are attributes.

Method Exception Condition
requestDevice() TypeError A filter or exclusion filter is invalid: productId without vendorId, subclassCode without classCode, protocolCode without subclassCode.
requestDevice() SecurityError No transient activation, or the usb Permissions Policy feature is disallowed for the document.
requestDevice() NotFoundError The user dismissed the chooser, or no attached device matched the filters.
any USBDevice method NotFoundError The device is no longer connected.
selectConfiguration(), claimInterface(), selectAlternateInterface(), clearHalt(), transfers NotFoundError The configuration value, interface number, alternate setting, or endpoint does not exist.
selectConfiguration(), claimInterface(), transfers InvalidStateError The device is not open, no configuration is selected, or (for transfers and selectAlternateInterface()) the interface is not claimed.
claimInterface() SecurityError The interface belongs to a protected class (for example HID or mass storage) and the context is not unrestricted.
transferIn(), transferOut() InvalidAccessError The endpoint is not "bulk" or "interrupt".
isochronousTransferIn(), isochronousTransferOut() InvalidAccessError The endpoint is not "isochronous".
open(), selectConfiguration(), claimInterface(), releaseInterface(), selectAlternateInterface(), clearHalt(), reset(), transfers NetworkError The platform operation or the transfer failed for a reason other than a stall or babble.
pending transfers AbortError close(), releaseInterface(), or selectAlternateInterface() aborted them.

The specification lists also devices on its blocklist (matched by idVendor, idProduct, and bcdDevice); these are excluded from the chooser rather than rejected with an exception. Chromium adds TimeoutError for a transfer that times out, DataError for isochronous packet lengths that do not match the buffer, and IndexSizeError for an endpoint number above 15.

Each example checks navigator.usb first and shows what happens without it. Only requestDevice() needs a click; the transfers can run from any later code.

Pairing a device by vendor ID with a reuse path and a fallback

Section titled “Pairing a device by vendor ID with a reuse path and a fallback”

getDevices() returns previously granted devices, so the chooser appears only on the first visit. Without the API the function returns a reason the UI can show, such as a link to a desktop browser or a native tool.

async function pair(vendorId) {
if (!navigator.usb) {
return { device: null, reason: 'WebUSB is not available in this browser.' };
}
const granted = await navigator.usb.getDevices();
let device = granted.find((d) => d.vendorId === vendorId);
if (!device) {
try {
device = await navigator.usb.requestDevice({ filters: [{ vendorId }] });
} catch (err) {
if (err.name === 'NotFoundError') return { device: null, reason: 'No device chosen.' };
throw err;
}
}
await device.open();
if (device.configuration === null) await device.selectConfiguration(1);
return { device, reason: null };
}

device.configuration is already set on most operating systems after open(); the explicit selectConfiguration(1) covers the case where it is null.

Claiming an interface and running a bulk round trip

Section titled “Claiming an interface and running a bulk round trip”

Find the first vendor-specific interface (interfaceClass 0xFF), claim it, locate its bulk endpoints, and exchange one packet. A SecurityError here means the interface is a protected class and WebUSB will not hand it over; a NetworkError means the OS driver still owns it.

async function roundTrip(device, payload) {
const iface = device.configuration.interfaces.find((i) =>
i.alternates.some((a) => a.interfaceClass === 0xff),
);
if (!iface) throw new Error('No vendor-specific interface on this device.');
await device.claimInterface(iface.interfaceNumber);
const alt = iface.alternates[0];
const outEp = alt.endpoints.find((e) => e.direction === 'out' && e.type === 'bulk');
const inEp = alt.endpoints.find((e) => e.direction === 'in' && e.type === 'bulk');
const sent = await device.transferOut(outEp.endpointNumber, payload);
if (sent.status === 'stall') await device.clearHalt('out', outEp.endpointNumber);
const reply = await device.transferIn(inEp.endpointNumber, inEp.packetSize);
await device.releaseInterface(iface.interfaceNumber);
return reply.status === 'ok' ? new Uint8Array(reply.data.buffer) : null;
}

A "stall" status is not an exception: the device halted the endpoint, and clearHalt() is the documented recovery before retrying.

Dropping the connection when the device is unplugged

Section titled “Dropping the connection when the device is unplugged”

Granted devices announce their removal through the disconnect event on navigator.usb. Compare event.device with the device in use so unrelated hardware does not reset the UI.

function watch(device, onLost) {
if (!navigator.usb) return () => {};
const handler = (event) => {
if (event.device === device) onLost();
};
navigator.usb.addEventListener('disconnect', handler);
return () => navigator.usb.removeEventListener('disconnect', handler);
}

Transfers still pending at disconnect time reject with NotFoundError, so onLost is also the place to discard any queue of outstanding promises.

Specifications

SpecificationStatus
WebUSB API: requestDevice() methodWHATWG living standard
WebUSB API: claimInterface() methodWHATWG living standard
WebUSB API: blocklistWHATWG living standard