Integrating Web Serial API for COM Port Communication in Browsers

The label printer won't print, Arduino doesn't respond, industrial sensor is silent — all due to the lack of browser-to-COM port connection. **Web Serial API** solves this: the browser gains direct access to the serial port without installing drivers or native applications. We provide complete COM p

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1414
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    982
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    995

The label printer won't print, Arduino doesn't respond, industrial sensor is silent — all due to the lack of browser-to-COM port connection. Web Serial API solves this: the browser gains direct access to the serial port without installing drivers or native applications. We provide complete COM port integration with web applications designed for industrial equipment web control, barcode scanners, and Arduino web projects. We have been implementing this solution for over 5 years (since 2019) and completed more than 50 projects for industrial sensors, medical devices, and POS terminals. License savings reach 50%, maintenance costs are reduced by 40%. Integration costs start from $800.

Why Use Web Serial API over Native Apps?

It simplifies deployment 10x: no drivers to install, no app updates, no environment configuration. The user simply opens a page in a Chromium browser and works. Comparison with traditional approaches:

Criteria Web Serial API Java Applet Electron App
Installation Not required JRE required Installation required
Update Automatic (web) Manual Manual
Security Same-origin, HTTPS Questionable Depends on build
Device support USB/Bluetooth/COM Only COM Via Native Messaging

The API is a modern, secure solution. We implement it turnkey in 2–4 days.

Browser Support and Limitations

The API is supported in Chrome 89+ (since March 2021), Edge 89+, Opera 75+. Firefox and Safari do not support it. Therefore, the page must either require a Chromium browser or provide a Serial API fallback.

Support check:

if (!('serial' in navigator)) { throw new Error('Web Serial API is not supported. Use Chrome 89+') } 

Origin permission: In production, you must add the header Permissions-Policy: serial=*.

Ensuring Operation in Unsupported Browsers

If your audience uses Firefox or Safari, provide a fallback. We offer three approaches:

Fallback Method Description Complexity
Manual Input User enters data manually via a form Minimal
File Upload Data exported from the device to a file, then uploaded Low
WebSocket Agent A local agent on the device transmits data via WebSocket Medium

Choice depends on data type and automation requirements. For example, for a barcode scanner, file upload is sufficient; for interactive Arduino control, the web app will require a WebSocket agent.

Service Architecture: Isolating Port Work

We isolate all port work in a SerialService class. The UI component doesn't know about streams and buffers — it calls the service methods and receives data via callbacks or EventEmitter.

type SerialDataHandler = (data: Uint8Array) => void type SerialErrorHandler = (error: Error) => void interface SerialConfig { baudRate: number // baud rate configuration dataBits?: 7 | 8 stopBits?: 1 | 2 parity?: 'none' | 'even' | 'odd' bufferSize?: number flowControl?: 'none' | 'hardware' } class SerialService extends EventTarget { private port: SerialPort | null = null private reader: ReadableStreamDefaultReader<Uint8Array> | null = null private writer: WritableStreamDefaultWriter<Uint8Array> | null = null private readLoopActive = false async requestPort(filters: SerialPortFilter[] = []): Promise<void> { this.port = await navigator.serial.requestPort({ filters }) } async connect(config: SerialConfig): Promise<void> { if (!this.port) throw new Error('No port selected') await this.port.open({ baudRate: config.baudRate, dataBits: config.dataBits ?? 8, stopBits: config.stopBits ?? 1, parity: config.parity ?? 'none', bufferSize: config.bufferSize ?? 4096, flowControl: config.flowControl ?? 'none', }) this.writer = this.port.writable!.getWriter() this.startReadLoop() } private async startReadLoop(): Promise<void> { if (!this.port?.readable) return this.readLoopActive = true while (this.port.readable && this.readLoopActive) { this.reader = this.port.readable.getReader() try { while (true) { const { value, done } = await this.reader.read() if (done) break if (value) { this.dispatchEvent( Object.assign(new Event('data'), { detail: value }) ) } } } catch (error) { if (this.readLoopActive) { this.dispatchEvent( Object.assign(new Event('error'), { detail: error }) ) } } finally { this.reader.releaseLock() } } } async write(data: Uint8Array | string): Promise<void> { if (!this.writer) throw new Error('Port not open') const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data await this.writer.write(bytes) } async disconnect(): Promise<void> { this.readLoopActive = false this.reader?.cancel() this.writer?.releaseLock() await this.port?.close() this.port = null this.reader = null this.writer = null } get isConnected(): boolean { return this.port !== null && this.port.readable !== null } } 

For more details on Serial API methods, refer to the official documentation on MDN.

How to Work with Protocols: Adapter for Request-Response

Most devices use text or binary protocols over UART browser communication. Example for a device with a request-response protocol using \r\n delimiters:

class LineProtocolAdapter { private buffer = '' private pendingResolvers: Array<(line: string) => void> = [] constructor(private serial: SerialService) { serial.addEventListener('data', (e: Event) => { const event = e as Event & { detail: Uint8Array } this.buffer += new TextDecoder().decode(event.detail) this.flushLines() }) } private flushLines(): void { const lines = this.buffer.split('\r\n') this.buffer = lines.pop() ?? '' for (const line of lines) { if (line.trim()) { const resolver = this.pendingResolvers.shift() if (resolver) resolver(line.trim()) } } } async sendCommand(command: string, timeoutMs = 2000): Promise<string> { return new Promise((resolve, reject) => { const timer = setTimeout(() => { this.pendingResolvers = this.pendingResolvers.filter(r => r !== resolve) reject(new Error(`Timeout: no response to "${command}" in ${timeoutMs}ms`)) }, timeoutMs) this.pendingResolvers.push((line) => { clearTimeout(timer) resolve(line) }) this.serial.write(command + '\r\n').catch(reject) }) } } // Usage: const adapter = new LineProtocolAdapter(serialService) const version = await adapter.sendCommand('VERSION') const sensorData = await adapter.sendCommand('READ SENSOR 1') 

To filter devices by USB Vendor/Product ID, use navigator.serial.requestPort() with filters option. After initial authorization, you can restore the port without a dialog via navigator.serial.getPorts(). This enables USB auto-connect reconnection. For binary protocols, work directly with Uint8Array.

How to Implement Integration: Step-by-Step Plan

  1. Analyze the device protocol. Study the documentation: baud rate configuration, parity, command format. Collect test data.
  2. Configure the port. Open the port with required parameters via SerialPort.open(). Use the SerialService class from the example.
  3. Implement the protocol adapter. For text protocols, write LineProtocolAdapter; for binary protocols, work directly with Uint8Array.
  4. Integration with UI. Wrap the service in a React Serial Service (hook useSerialService) or Vue-composable. Add error handling and reconnection.
  5. Testing. Test on a real device or emulator (socat/VSPE). Ensure Serial API fallback works.
Typical Integration Mistakes
  • Forgot to trim extra characters from the buffer — get garbage.
  • Did not handle device disconnection — port hangs.
  • Use incorrect baud rate — data not readable.
  • Did not implement command timeout — application hangs forever.

What's Included in the Work

  • Protocol analysis of the target device
  • Configuration of port parameters (baud rate configuration, parity, flow control)
  • Implementation of SerialService and protocol adapter classes
  • React hook or Vue-composable
  • Reconnection handling
  • Fallback for unsupported browsers
  • Testing on real hardware or emulator

If the device uses a proprietary binary protocol, additional time is required for reverse engineering or studying the documentation. We also guarantee support for the developed solution and free consultations for one month after deployment.

Timeline: 2–4 days depending on device protocol complexity. Place an order — our engineers will adapt the solution to your equipment. Prices start at $800 for a standard integration.