Web Serial API is a browser API that lets a web page open a serial port and exchange bytes with a device such as a microcontroller board, a 3D printer or a lab instrument. A serial port sends data one bit at a time. The API is exposed as navigator.serial in secure contexts, uses streams for data, and is a Web Platform Incubator Community Group draft.
Choosing a port needs a prompt, so navigator.serial.requestPort() requires transient activation, for example a click. Without it the promise rejects with a SecurityError. An optional filters list narrows the chooser, for instance to USB devices with usbVendorId 0x2341. After the first grant, navigator.serial.getPorts() returns the allowed ports with no prompt.
The page then calls port.open() with SerialOptions. The only required member is baudRate, because there is no standard speed. The other members and their defaults, taken from the spec, are:
open() rejects with a TypeError when baudRate is 0, dataBits is not 7 or 8, stopBits is not 1 or 2, or bufferSize is 0. After it resolves, port.readable is a ReadableStream and port.writable is a WritableStream. A second open() on a port that is not closed rejects with InvalidStateError. Other calls are setSignals(), getSignals(), close() and forget().
Streams deliver chunks, not lines. A read can end in the middle of a message, so buffer text until you see a newline. This sample checks the options and then splits split-up chunks into lines, using the same stream classes as a real port.
function validate(o) {
if (o.baudRate === undefined || o.baudRate === 0) return "TypeError: baudRate";
const dataBits = o.dataBits ?? 8, stopBits = o.stopBits ?? 1, bufferSize = o.bufferSize ?? 255;
if (dataBits !== 7 && dataBits !== 8) return "TypeError: dataBits";
if (stopBits !== 1 && stopBits !== 2) return "TypeError: stopBits";
if (bufferSize === 0) return "TypeError: bufferSize";
return `ok baud=${o.baudRate} data=${dataBits} stop=${stopBits} parity=${o.parity ?? "none"} buffer=${bufferSize}`;
}
console.log(validate({ baudRate: 115200 }));
console.log(validate({ baudRate: 0 }));
console.log(validate({ baudRate: 9600, dataBits: 9 }));
const chunks = ["tem", "p=21.5\nte", "mp=21.7\n"];
const src = new ReadableStream({ start(c) { chunks.forEach(x => c.enqueue(new TextEncoder().encode(x))); c.close(); } });
const lines = src.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream({
buf: "", transform(chunk, ctl) { this.buf += chunk; const p = this.buf.split("\n"); this.buf = p.pop(); p.forEach(l => ctl.enqueue(l)); }
}));
(async () => { for await (const l of lines) console.log("line:", l); })();
Output:
ok baud=115200 data=8 stop=1 parity=none buffer=255
TypeError: baudRate
TypeError: dataBits
line: temp=21.5
line: temp=21.7
The Web Serial API works in Chrome and Edge 89 and later, Opera 75 and later, and Chrome on Android 148 and later. MDN compatibility data lists Firefox 151 and no Safari support. Check "serial" in navigator before showing a connect button.
The readable stream errors with a named DOMException, such as BufferOverrunError, BreakError, FramingError, ParityError or UnknownError. The spec treats errors like a parity failure as recoverable, so you can open a new reader and keep reading. A fatal error, such as unplugging the device, makes port.readable become null, and your read loop should end.