WebUSB in Chrome Extensions: Communicate with USB Devices (2026)
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
| Method | Direction | Use Case |
|---|---|---|
transferOut(endpoint, data) | Host → Device | Send commands, data |
transferIn(endpoint, length) | Device → Host | Read responses, sensor data |
controlTransferOut(setup, data) | Host → Device | Configuration, setup commands |
controlTransferIn(setup, length) | Device → Host | Read 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
I Built the Same Chrome Extension With 5 Different Frameworks. Here's What Actually Happened.
WXT vs Plasmo vs CRXJS vs Extension.js vs Bedframe. Real benchmarks, honest opinions, and the framework with 12K stars that's quietly dying.
5 Best Email Marketing Services to Grow Your Chrome Extension (2026)
Compare the top email marketing platforms for SaaS and Chrome extension developers. MailerLite, Mailchimp, Brevo, ActiveCampaign, and Drip reviewed.
15 Best Practices to Build a Browser Extension That Users Love (2026 Guide)
Master browser extension development in 2026. Manifest V3, security, performance, and UX best practices to build extensions users love.