# WebUSB API

> navigator.usb.requestDevice() pairs a page with a USB device that has no class driver: filters, USBDevice transfers, every exception, and runnable examples.

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](https://github.com/mozilla/standards-positions/issues/100)); Safari has none; Android WebView exposes `navigator.usb` but every call fails ([crbug.com/41441927](https://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

```js
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

`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

| 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.

:::observed
Chrome's messages, from [`usb.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/webusb/usb.cc) and [`usb_device.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/webusb/usb_device.cc) (chromium.googlesource.com): `requestDevice()` outside a gesture rejects with `SecurityError: Must be handling a user gesture to show a permission request.`; cancelling the chooser rejects with `NotFoundError: No device selected.`; a `productId` filter without `vendorId` throws `TypeError: A filter containing a productId must also contain a vendorId.`; `claimInterface()` before `open()` rejects with `InvalidStateError: The device must be opened first.`; claiming a HID or mass-storage interface rejects with `SecurityError: The requested interface implements a protected class.`; a failed claim (the OS driver still holds the interface, common on Windows without WinUSB) rejects with `NetworkError: Unable to claim interface.`.
:::

## 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

`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.

```js
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

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.

```js
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

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.

```js
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

- [Web Serial API](/reference/capabilities/web-serial/), for devices that speak a serial protocol
- [WebHID API](/reference/capabilities/web-hid/), for the HID class WebUSB refuses
- [Web Bluetooth API](/reference/capabilities/web-bluetooth/)
- [WebUSB API: requestDevice() method](https://usb.spec.whatwg.org/#dom-usb-requestdevice) (whatwg.org)
- [WebUSB API: open() method](https://usb.spec.whatwg.org/#dom-usbdevice-open) (whatwg.org)
- [Access USB devices on the web](https://developer.chrome.com/docs/capabilities/usb) (developer.chrome.com)
- [Mozilla standards position: WebUSB](https://github.com/mozilla/standards-positions/issues/100) (github.com)
- [Chromium bug 41441927: WebUSB in Android WebView](https://crbug.com/41441927) (crbug.com)