WebUSB in Chrome Extensions: Communicate with USB Devices (2026)

AppBooster Team · · 3 min read
USB devices and cables on a desk

What Is WebUSB and Why Use It in Extensions?

WebUSB lets your Chrome extension communicate directly with USB devices — Arduino boards, 3D printers, scientific instruments, firmware flashers, and any non-standard USB peripheral. Unlike WebHID (which handles HID-class devices), WebUSB works with custom USB protocols.

Requirements: Chrome 118+. No manifest permissions needed — the browser handles the permission flow.


The Service Worker Limitation

Like WebHID, navigator.usb.requestDevice() cannot be called from a service worker. You must request device access from a popup, side panel, or options page.

Active USB connections keep the service worker alive.


Implementation

popup.js — Device Selection

document.getElementById('connect-btn').addEventListener('click', async () => {
  try {
    const device = await navigator.usb.requestDevice({
      filters: [
        { vendorId: 0x2341 },              // Arduino
        { vendorId: 0x1A86, productId: 0x7523 } // CH340 USB-Serial
      ]
    });

    if (device) {
      document.getElementById('status').textContent =
        `Selected: ${device.productName} (${device.manufacturerName})`;
      chrome.runtime.sendMessage({ action: 'usbDeviceSelected' });
    }
  } catch (error) {
    if (error.name !== 'NotFoundError') { // User cancelled
      document.getElementById('status').textContent = `Error: ${error.message}`;
    }
  }
});

service-worker.js — Device Communication

chrome.runtime.onMessage.addListener(async (message) => {
  if (message.action === 'usbDeviceSelected') {
    const devices = await navigator.usb.getDevices();

    for (const device of devices) {
      await device.open();
      await device.selectConfiguration(1);
      await device.claimInterface(0);

      // Send data to device
      const encoder = new TextEncoder();
      const data = encoder.encode('Hello USB!');
      await device.transferOut(1, data);

      // Receive data from device
      const result = await device.transferIn(1, 64);
      const decoder = new TextDecoder();
      const received = decoder.decode(result.data);
      console.log(`Received: ${received}`);
    }
  }
});

USB Transfer Types

MethodDirectionUse Case
transferOut(endpoint, data)Host → DeviceSend commands, data
transferIn(endpoint, length)Device → HostRead responses, sensor data
controlTransferOut(setup, data)Host → DeviceConfiguration, setup commands
controlTransferIn(setup, length)Device → HostRead device status

Control Transfer Example

// Read device descriptor
const result = await device.controlTransferIn({
  requestType: 'standard',
  recipient: 'device',
  request: 0x06,  // GET_DESCRIPTOR
  value: 0x0100,  // Device descriptor
  index: 0
}, 18);

Arduino Communication Example

async function setupArduino(device) {
  await device.open();

  if (device.configuration === null) {
    await device.selectConfiguration(1);
  }

  await device.claimInterface(2); // CDC interface

  // Set baud rate to 9600 (CDC ACM SET_LINE_CODING)
  const lineEncoding = new ArrayBuffer(7);
  const view = new DataView(lineEncoding);
  view.setUint32(0, 9600, true);  // Baud rate
  view.setUint8(4, 0);            // Stop bits (1)
  view.setUint8(5, 0);            // Parity (none)
  view.setUint8(6, 8);            // Data bits

  await device.controlTransferOut({
    requestType: 'class',
    recipient: 'interface',
    request: 0x20,  // SET_LINE_CODING
    value: 0,
    index: 2
  }, lineEncoding);

  return device;
}

async function readFromArduino(device) {
  const result = await device.transferIn(5, 64);
  return new TextDecoder().decode(result.data);
}

async function writeToArduino(device, message) {
  const data = new TextEncoder().encode(message + '\n');
  await device.transferOut(4, data);
}

Device Connection Events

navigator.usb.addEventListener('connect', (event) => {
  console.log(`USB connected: ${event.device.productName}`);
});

navigator.usb.addEventListener('disconnect', (event) => {
  console.log(`USB disconnected: ${event.device.productName}`);
  chrome.action.setBadgeText({ text: '!' });
  chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
});

Error Handling

async function safeTransfer(device, endpoint, data) {
  try {
    const result = await device.transferOut(endpoint, data);
    if (result.status !== 'ok') {
      throw new Error(`Transfer failed: ${result.status}`);
    }
    return result;
  } catch (error) {
    if (error.message.includes('disconnected')) {
      chrome.runtime.sendMessage({ action: 'deviceDisconnected' });
    }
    throw error;
  }
}

What’s Next

WebUSB brings hardware integration directly into Chrome extensions — no drivers, no native apps. Start with device selection in your popup, handle communication in the service worker, and always clean up connections on disconnect.

Build powerful hardware-connected extensions with help from ExtensionBooster.

Share this article

Build better extensions with free tools

Icon generator, MV3 converter, review exporter, and more — no signup needed.

Related Articles