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).
Syntax
Section titled “Syntax”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].
Parameters
Section titled “Parameters”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.
Exceptions
Section titled “Exceptions”| 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.
Examples
Section titled “Examples”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.
See also
Section titled “See also”- Web Serial API, for devices that speak a serial protocol
- WebHID API, for the HID class WebUSB refuses
- Web Bluetooth API
- WebUSB API: requestDevice() method (whatwg.org)
- WebUSB API: open() method (whatwg.org)
- Access USB devices on the web (developer.chrome.com)
- Mozilla standards position: WebUSB (github.com)
- Chromium bug 41441927: WebUSB in Android WebView (crbug.com)
Specifications
| Specification | Status |
|---|---|
| WebUSB API: requestDevice() method | WHATWG living standard |
| WebUSB API: claimInterface() method | WHATWG living standard |
| WebUSB API: blocklist | WHATWG living standard |