Glossary

Web Serial API

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.

How it works

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:

  • dataBits: 8, and the only other legal value is 7
  • stopBits: 1, and the only other legal value is 2
  • parity: "none"
  • bufferSize: 255
  • flowControl: "none"

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

Which browsers support the Web Serial API?

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.

What happens when a Web Serial read fails?

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.

Common pitfalls

  • Assuming a default baud rate: baudRate is required and must be nonzero, so match the device documentation exactly or you get garbage bytes.
  • Treating a chunk as a line: reads split anywhere. Buffer until the delimiter, as shown above.
  • Closing with a reader still active: exit the read loop first, call reader.cancel(), then releaseLock(), and only then await port.close(), as the spec example does.
  • Calling requestPort() without a click: it rejects with SecurityError.
  • Expecting every port in the chooser: Bluetooth ports with custom service class IDs are hidden unless you list the ID in allowedBluetoothServiceClassIds.
  • Using an iframe without permission: the "serial" feature can be blocked by Permissions-Policy.

Related terms

  • WebUSB — raw access to USB devices that are not serial ports
  • Web Bluetooth — wireless alternative using GATT services
  • HTTPS — the secure context the API requires
  • Permissions-Policy header — can disable the serial feature in frames
  • Promise — every Web Serial method returns one

See also

  • Term: WebUSB — the closest alternative when the device is not a serial port