Web APIs, todayLab · Alex Harto

Web Serial

Serial ports from the browser: Arduino, ESP32, 3D printers, lab instruments.

In your browser: checking…Reviewed 1 Oct 2026

requestPort() asks the user for a port (USB or Bluetooth), open() sets the speed, and then you read and write with streams. It is used to flash firmware, for debug consoles or to configure devices with nothing to install.

Desktop Chrome and Edge since 89. Firefox shipped it in 151 (May 2026) with a site-permission add-on the browser generates itself. Chrome for Android since 148; over USB only on devices with the new Android serial API. Not Safari.

What it is for

  • Flashing boards from the web
  • Serial consoles and microcontroller debugging
  • Driving 3D printers, CNC machines and instruments

Demo

A complete serial terminal: connect an Arduino, an ESP32, a micro:bit or any USB-serial adapter, choose the speed and type. The minimal example includes an Arduino sketch that echoes what it receives.

Browser support

Limited availability
FeatureDesktopMobile
ChromeEdgeFirefoxSafariChrome AndroidFirefox AndroidSafari iOS
navigator.serial89Chrome: 89 89Edge: 89 1511Firefox: 151 —Safari: No1482Chrome: 148 —Firefox: No—Safari: No
  1. 1Desktop. The first time it asks you to install a site-permission add-on.
  2. 2Bluetooth (RFCOMM) since 138; USB since 148 only on devices with the new Android serial API.

Compatibility generated on every deploy from MDN browser-compat-data 8.1.3 (24 Sep 2026) · web-features 3.40.0 · checked by hand: 1 Oct 2026

Minimal code

<!doctype html>
<meta charset="utf-8">
<title>Web Serial · ejemplo mínimo</title>
<!-- Terminal serie: abre un puerto (Arduino, micro:bit, ESP32…), muestra lo que llega y envía líneas.
     Chrome y Edge 89+; Firefox 151+ (pide instalar un complemento de permiso); Chrome Android 148+. HTTPS o localhost. -->
<button id="connect">Abrir puerto</button>
<input id="line" placeholder="Texto a enviar" disabled> <button id="send" disabled>Enviar</button>
<pre id="out"></pre>
<script>
  const $ = (s) => document.querySelector(s);
  let writer;

  $('#connect').onclick = async () => {
    if (!('serial' in navigator)) return ($('#out').textContent = 'Web Serial no está disponible en este navegador.');
    const port = await navigator.serial.requestPort();
    await port.open({ baudRate: 115200 });

    // Escribir: texto → bytes
    const encoder = new TextEncoderStream();
    encoder.readable.pipeTo(port.writable);
    writer = encoder.writable.getWriter();
    $('#line').disabled = $('#send').disabled = false;

    // Leer: bytes → texto, hasta que se cierre el puerto
    const reader = port.readable.pipeThrough(new TextDecoderStream()).getReader();
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      $('#out').textContent += value;
    }
  };

  $('#send').onclick = async () => {
    await writer.write($('#line').value + '\n');
    $('#line').value = '';
  };
</script>

What’s new

  1. WHATWG takes in Web Serial in its new hardware workstream.
  2. Firefox 151 ships Web Serial on desktop, gated by a site-permission add-on.
  3. Chrome 148 for Android supports USB ports on compatible devices.

Keep in mind

  1. requestPort() needs a user gesture; getPorts() returns already-authorised ports.
  2. Before closing, cancel the reader and release the writer; otherwise port.close() fails.
  3. In Firefox for enterprise it is off by default (DefaultSerialGuardSetting).

Further reading

Sources