WebUSB API: How JavaScript Talks to USB Devices

The WebUSB API is a modern web standard that allows web applications to communicate directly with Universal Serial Bus (USB) devices from within the browser. Traditionally, hardware communication required native applications, specialized software development kits (SDKs), or dedicated system drivers. WebUSB eliminates these barriers by providing a secure, standard JavaScript interface to identify, connect, and transfer data to compatible hardware. This article explains how WebUSB works, the underlying communication process in JavaScript, its security model, and common practical applications.

Understanding the WebUSB API

WebUSB bridges the gap between web platforms and physical hardware. Built primarily for Chromium-based browsers, it enables web pages to discover and interact with non-standard USB devices—such as microcontrollers, scientific instruments, receipt printers, and custom electronics—without requiring end-users to install specific desktop drivers.

Standard peripherals like keyboards, mice, and mass storage devices are generally blocked by the browser to protect core system input/output mechanisms. WebUSB targets custom and specialized devices that utilize vendor-specific interfaces.

How JavaScript Communicates with USB Devices

Communication between a web page and a USB device follows a standardized lifecycle: discovery, connection, interface configuration, and data transfer.

1. Requesting Device Access

For security reasons, a website cannot scan for connected USB devices automatically. Access must be initiated by an explicit user gesture, such as clicking a button.

JavaScript calls the navigator.usb.requestDevice() method, passing filters that specify vendor IDs (VID) and product IDs (PID):

const connectButton = document.querySelector('#connect');

connectButton.addEventListener('click', async () => {
  try {
    const device = await navigator.usb.requestDevice({
      filters: [{ vendorId: 0x2341 }] // Example: Arduino Vendor ID
    });
    console.log(`Connected to: ${device.productName}`);
  } catch (error) {
    console.error('Connection failed:', error);
  }
});

2. Opening and Configuring the Device

Once the user selects a device from the browser prompt and grants permission, JavaScript opens a session, selects an active configuration, and claims the target interface:

await device.open();
await device.selectConfiguration(1);
await device.claimInterface(0);

Claiming an interface ensures exclusive communication rights with that specific capability of the hardware.

3. Transferring Data

Data exchange occurs via standard USB transfer types using endpoints:

Sending data to an endpoint:

const encoder = new TextEncoder();
const data = encoder.encode('HELLO_DEVICE');
await device.transferOut(1, data); // Sends data to endpoint 1

Receiving data from an endpoint:

const result = await device.transferIn(1, 64); // Reads up to 64 bytes from endpoint 1
const decoder = new TextDecoder();
const message = decoder.decode(result.data);
console.log(`Received: ${message}`);

4. Closing the Connection

When communication is complete, the application releases the claimed interfaces and closes the device session:

await device.releaseInterface(0);
await device.close();

Security and Permissions

WebUSB implements strict security constraints to prevent malicious hardware access:

Common Use Cases