Read from and write to a serial port Stay organized with collections Save and categorize content based on your preferences.
The Web Serial API allows websites to communicate with serial devices.
Success: The Web Serial API, part of thecapabilities project, launchedin Chrome 89.What is the Web Serial API?
A serial port is a bidirectional communication interface that allows sending andreceiving data byte by byte.
The Web Serial API provides a way for websites to read from and write to aserial device with JavaScript. Serial devices are connected either through aserial port on the user's system or through removable USB and Bluetooth devicesthat emulate a serial port.
In other words, the Web Serial API bridges the web and the physical world byallowing websites to communicate with serial devices, such as microcontrollersand 3D printers.
This API is also a great companion toWebUSB as operating systems requireapplications to communicate with some serial ports using their higher-levelserial API rather than the low-level USB API.
Suggested use cases
In the educational, hobbyist, and industrial sectors, users connect peripheraldevices to their computers. These devices are often controlled bymicrocontrollers via a serial connection used by custom software. Some customsoftware to control these devices is built with web technology:
In some cases, websites communicate with the device through an agentapplication that users installed manually. In others, the application isdelivered in a packaged application through a framework such as Electron.And in others, the user is required to perform an additional step such ascopying a compiled application to the device via a USB flash drive.
In all these cases, the user experience will be improved by providing directcommunication between the website and the device that it is controlling.
Current status
Step | Status |
---|---|
1. Create explainer | Complete |
2. Create initial draft of specification | Complete |
3. Gather feedback & iterate on design | Complete |
4. Origin trial | Complete |
5. Launch | Complete |
Using the Web Serial API
Feature detection
To check if the Web Serial API is supported, use:
if("serial"innavigator){// The Web Serial API is supported.}
Open a serial port
The Web Serial API is asynchronous by design. This prevents the website UI fromblocking when awaiting input, which is important because serial data can bereceived at any time, requiring a way to listen to it.
To open a serial port, first access aSerialPort
object. For this, you caneither prompt the user to select a single serial port by callingnavigator.serial.requestPort()
in response to a user gesture such as touchor mouse click, or pick one fromnavigator.serial.getPorts()
which returnsa list of serial ports the website has been granted access to.
document.querySelector('button').addEventListener('click',async()=>{// Prompt user to select any serial port.constport=awaitnavigator.serial.requestPort();});
// Get all serial ports the user has previously granted the website access to.constports=awaitnavigator.serial.getPorts();
Thenavigator.serial.requestPort()
function takes an optional object literalthat defines filters. Those are used to match any serial device connected overUSB with a mandatory USB vendor (usbVendorId
) and optional USB productidentifiers (usbProductId
).
// Filter on devices with the Arduino Uno USB Vendor/Product IDs.constfilters=[{usbVendorId:0x2341,usbProductId:0x0043},{usbVendorId:0x2341,usbProductId:0x0001}];// Prompt user to select an Arduino Uno device.constport=awaitnavigator.serial.requestPort({filters});const{usbProductId,usbVendorId}=port.getInfo();

CallingrequestPort()
prompts the user to select a device and returns aSerialPort
object. Once you have aSerialPort
object, callingport.open()
with the desired baud rate will open the serial port. ThebaudRate
dictionarymember specifies how fast data is sent over a serial line. It is expressed inunits of bits-per-second (bps). Check your device's documentation for thecorrect value as all the data you send and receive will be gibberish if this isspecified incorrectly. For some USB and Bluetooth devices that emulate a serialport this value may be safely set to any value as it is ignored by theemulation.
// Prompt user to select any serial port.constport=awaitnavigator.serial.requestPort();// Wait for the serial port to open.awaitport.open({baudRate:9600});
You can also specify any of the options below when opening a serial port. Theseoptions are optional and have convenientdefault values.
dataBits
: The number of data bits per frame (either 7 or 8).stopBits
: The number of stop bits at the end of a frame (either 1 or 2).parity
: The parity mode (either"none"
,"even"
or"odd"
).bufferSize
: The size of the read and write buffers that should be created(must be less than 16MB).flowControl
: The flow control mode (either"none"
or"hardware"
).
Read from a serial port
Input and output streams in the Web Serial API are handled by the Streams API.
Note: If streams are new to you, check outStreams APIconcepts.This article barely scratches the surface of streams and stream handling.After the serial port connection is established, thereadable
andwritable
properties from theSerialPort
object return aReadableStream and aWritableStream. Those will be used to receive data from and send data to theserial device. Both useUint8Array
instances for data transfer.
When new data arrives from the serial device,port.readable.getReader().read()
returns two properties asynchronously: thevalue
and adone
boolean. Ifdone
is true, the serial port has been closed or there is no more data comingin. Callingport.readable.getReader()
creates a reader and locksreadable
toit. Whilereadable
islocked, the serial port can't be closed.
constreader=port.readable.getReader();// Listen to data coming from the serial device.while(true){const{value,done}=awaitreader.read();if(done){// Allow the serial port to be closed later.reader.releaseLock();break;}// value is a Uint8Array.console.log(value);}
Some non-fatal serial port read errors can happen under some conditions such asbuffer overflow, framing errors, or parity errors. Those are thrown asexceptions and can be caught by adding another loop on top of the previous onethat checksport.readable
. This works because as long as the errors arenon-fatal, a newReadableStream is created automatically. If a fatal erroroccurs, such as the serial device being removed, thenport.readable
becomesnull.
while(port.readable){constreader=port.readable.getReader();try{while(true){const{value,done}=awaitreader.read();if(done){//Allowtheserialporttobeclosedlater.reader.releaseLock();break;}if(value){console.log(value);}}}catch(error){//TODO:Handlenon-fatalreaderror.}}
If the serial device sends text back, you can pipeport.readable
through aTextDecoderStream
as shown below. ATextDecoderStream
is atransform streamthat grabs allUint8Array
chunks and converts them to strings.
consttextDecoder=newTextDecoderStream();constreadableStreamClosed=port.readable.pipeTo(textDecoder.writable);constreader=textDecoder.readable.getReader();//Listentodatacomingfromtheserialdevice.while(true){const{value,done}=awaitreader.read();if(done){//Allowtheserialporttobeclosedlater.reader.releaseLock();break;}//valueisastring.console.log(value);}
You can take control of how memory is allocated when you read from the stream using a "Bring Your Own Buffer" reader. Callport.readable.getReader({ mode: "byob" })
to get theReadableStreamBYOBReader interface and provide your ownArrayBuffer
when callingread()
. Note that the Web Serial API supports this feature in Chrome 106 or later.
try{constreader=port.readable.getReader({mode:"byob"});// Call reader.read() to read data into a buffer...}catch(error){if(errorinstanceofTypeError){// BYOB readers are not supported.// Fallback to port.readable.getReader()...}}
Here's an example of how to reuse the buffer out ofvalue.buffer
:
constbufferSize=1024;// 1kBletbuffer=newArrayBuffer(bufferSize);// Set `bufferSize` on open() to at least the size of the buffer.awaitport.open({baudRate:9600,bufferSize});constreader=port.readable.getReader({mode:"byob"});while(true){const{value,done}=awaitreader.read(newUint8Array(buffer));if(done){break;}buffer=value.buffer;// Handle `value`.}
Here's another example of how to read a specific amount of data from a serial port:
asyncfunctionreadInto(reader,buffer){letoffset=0;while(offset <buffer.byteLength){const{value,done}=awaitreader.read(newUint8Array(buffer,offset));if(done){break;}buffer=value.buffer;offset+=value.byteLength;}returnbuffer;}constreader=port.readable.getReader({mode:"byob"});letbuffer=newArrayBuffer(512);// Read the first 512 bytes.buffer=awaitreadInto(reader,buffer);// Then read the next 512 bytes.buffer=awaitreadInto(reader,buffer);
Write to a serial port
To send data to a serial device, pass data toport.writable.getWriter().write()
. CallingreleaseLock()
onport.writable.getWriter()
is required for the serial port to be closed later.
constwriter=port.writable.getWriter();constdata=newUint8Array([104,101,108,108,111]);// helloawaitwriter.write(data);// Allow the serial port to be closed later.writer.releaseLock();
Send text to the device through aTextEncoderStream
piped toport.writable
as shown below.
consttextEncoder=newTextEncoderStream();constwritableStreamClosed=textEncoder.readable.pipeTo(port.writable);constwriter=textEncoder.writable.getWriter();awaitwriter.write("hello");
Close a serial port
port.close()
closes the serial port if itsreadable
andwritable
membersareunlocked, meaningreleaseLock()
has been called for their respectivereader and writer.
awaitport.close();
However, when continuously reading data from a serial device using a loop,port.readable
will always be locked until it encounters an error. In thiscase, callingreader.cancel()
will forcereader.read()
to resolveimmediately with{ value: undefined, done: true }
and therefore allowing theloop to callreader.releaseLock()
.
// Without transform streams.letkeepReading=true;letreader;asyncfunctionreadUntilClosed(){while(port.readable &&keepReading){reader=port.readable.getReader();try{while(true){const{value,done}=awaitreader.read();if(done){// reader.cancel() has been called.break;}// value is a Uint8Array.console.log(value);}}catch(error){// Handle error...}finally{// Allow the serial port to be closed later.reader.releaseLock();}}awaitport.close();}constclosedPromise=readUntilClosed();document.querySelector('button').addEventListener('click',async()=>{// User clicked a button to close the serial port.keepReading=false;// Force reader.read() to resolve immediately and subsequently// call reader.releaseLock() in the loop example above.reader.cancel();awaitclosedPromise;});
Closing a serial port is more complicated when usingtransform streams. Callreader.cancel()
as before.Then callwriter.close()
andport.close()
. This propagates errors throughthe transform streams to the underlying serial port. Because error propagationdoesn't happen immediately, you need to use thereadableStreamClosed
andwritableStreamClosed
promises created earlier to detect whenport.readable
andport.writable
have been unlocked. Cancelling thereader
causes thestream to be aborted; this is why you must catch and ignore the resulting error.
//Withtransformstreams.consttextDecoder=newTextDecoderStream();constreadableStreamClosed=port.readable.pipeTo(textDecoder.writable);constreader=textDecoder.readable.getReader();//Listentodatacomingfromtheserialdevice.while(true){const{value,done}=awaitreader.read();if(done){reader.releaseLock();break;}//valueisastring.console.log(value);}consttextEncoder=newTextEncoderStream();constwritableStreamClosed=textEncoder.readable.pipeTo(port.writable);reader.cancel();awaitreadableStreamClosed.catch(()=>{/*Ignoretheerror*/});writer.close();awaitwritableStreamClosed;awaitport.close();
Listen to connection and disconnection
If a serial port is provided by a USB device then that device may be connectedor disconnected from the system. When the website has been granted permission toaccess a serial port, it should monitor theconnect
anddisconnect
events.
navigator.serial.addEventListener("connect",(event)=>{// TODO: Automatically open event.target or warn user a port is available.});navigator.serial.addEventListener("disconnect",(event)=>{// TODO: Remove |event.target| from the UI.// If the serial port was opened, a stream error would be observed as well.});
connect
anddisconnect
events fired a customSerialConnectionEvent
object with the affectedSerialPort
interfaceavailable as theport
attribute. You may want to useevent.port || event.target
to handle the transition.Handle signals
After establishing the serial port connection, you can explicitly query and setsignals exposed by the serial port for device detection and flow control. Thesesignals are defined as boolean values. For example, some devices such as Arduinowill enter a programming mode if the Data Terminal Ready (DTR) signal istoggled.
Settingoutput signals and gettinginput signals are respectively done bycallingport.setSignals()
andport.getSignals()
. See usage examples below.
// Turn off Serial Break signal.awaitport.setSignals({break:false});// Turn on Data Terminal Ready (DTR) signal.awaitport.setSignals({dataTerminalReady:true});// Turn off Request To Send (RTS) signal.awaitport.setSignals({requestToSend:false});
constsignals=awaitport.getSignals();console.log(`Clear To Send:${signals.clearToSend}`);console.log(`Data Carrier Detect:${signals.dataCarrierDetect}`);console.log(`Data Set Ready:${signals.dataSetReady}`);console.log(`Ring Indicator:${signals.ringIndicator}`);
Transforming streams
When you receive data from the serial device, you won't necessarily get all ofthe data at once. It may be arbitrarily chunked. For more information, seeStreams API concepts.
To deal with this, you can use some built-in transform streams such asTextDecoderStream
or create your own transform stream which allows you toparse the incoming stream and return parsed data. The transform stream sitsbetween the serial device and the read loop that is consuming the stream. It canapply an arbitrary transform before the data is consumed. Think of it like anassembly line: as a widget comes down the line, each step in the line modifiesthe widget, so that by the time it gets to its final destination, it's a fullyfunctioning widget.

For example, consider how to create a transform stream class that consumes astream and chunks it based on line breaks. Itstransform()
method is calledevery time new data is received by the stream. It can either enqueue the data orsave it for later. Theflush()
method is called when the stream is closed, andit handles any data that hasn't been processed yet.
To use the transform stream class, you need to pipe an incoming stream throughit. In the third code example underRead from a serial port,the original input stream was only piped through aTextDecoderStream
, so weneed to callpipeThrough()
to pipe it through our newLineBreakTransformer
.
classLineBreakTransformer{constructor(){// A container for holding stream data until a new line.this.chunks="";}transform(chunk,controller){// Append new chunks to existing chunks.this.chunks+=chunk;// For each line breaks in chunks, send the parsed lines out.constlines=this.chunks.split("\r\n");this.chunks=lines.pop();lines.forEach((line)=>controller.enqueue(line));}flush(controller){// When the stream is closed, flush any remaining chunks out.controller.enqueue(this.chunks);}}
consttextDecoder=newTextDecoderStream();constreadableStreamClosed=port.readable.pipeTo(textDecoder.writable);constreader=textDecoder.readable.pipeThrough(newTransformStream(newLineBreakTransformer())).getReader();
For debugging serial device communication issues, use thetee()
method ofport.readable
to split the streams going to or from the serial device. The twostreams created can be consumed independently and this allows you to print oneto the console for inspection.
const[appReadable,devReadable]=port.readable.tee();// You may want to update UI with incoming data from appReadable// and log incoming data in JS console for inspection from devReadable.
Revoke access to a serial port
The website can clean up permissions to access a serial port it is no longerinterested in retaining by callingforget()
on theSerialPort
instance. Forexample, for an educational web application used on a shared computer with manydevices, a large number of accumulated user-generated permissions creates a pooruser experience.
// Voluntarily revoke access to this serial port.awaitport.forget();
Asforget()
is available in Chrome 103 or later, check if this feature issupported with the following:
if("serial"innavigator &&"forget"inSerialPort.prototype){// forget() is supported.}
Dev Tips
Debugging the Web Serial API in Chrome is easy with the internal page,about://device-log
where you can see all serial device related events in onesingle place.

Codelab
In theGoogle Developer codelab, you'll use the Web Serial API to interactwith aBBC micro:bit board to show images on its 5x5 LED matrix.
Browser support
The Web Serial API is available on all desktop platforms (ChromeOS, Linux, macOS,and Windows) in Chrome 89.
Polyfill
On Android, support for USB-based serial ports is possible using the WebUSB APIand theSerial API polyfill. This polyfill is limited to hardware andplatforms where the device is accessible via the WebUSB API because it has notbeen claimed by a built-in device driver.
Security and privacy
The spec authors have designed and implemented the Web Serial API using the coreprinciples defined inControlling Access to Powerful Web Platform Features,including user control, transparency, and ergonomics. The ability to use thisAPI is primarily gated by a permission model that grants access to only a singleserial device at a time. In response to a user prompt, the user must take activesteps to select a particular serial device.
To understand the security tradeoffs, check out thesecurity andprivacysections of the Web Serial API Explainer.
Feedback
The Chrome team would love to hear about your thoughts and experiences with theWeb Serial API.
Tell us about the API design
Is there something about the API that doesn't work as expected? Or are theremissing methods or properties that you need to implement your idea?
File a spec issue on theWeb Serial API GitHub repo or add yourthoughts to an existing issue.
Report a problem with the implementation
Did you find a bug with Chrome's implementation? Or is the implementationdifferent from the spec?
File a bug athttps://new.crbug.com. Be sure to include as muchdetail as you can, provide simple instructions for reproducing the bug, and haveComponents set toBlink>Serial
.
Show support
Are you planning to use the Web Serial API? Your public support helps the Chrometeam prioritize features and shows other browser vendors how critical it is tosupport them.
Send a tweet to@ChromiumDev using the hashtag#SerialAPI
and let us know where and how you're using it.
Helpful links
- Specification
- Tracking bug
- ChromeStatus.com entry
- Blink Component:
Blink>Serial
Demos
Acknowledgements
Thanks toReilly Grant andJoe Medley for their reviews of this article.Aeroplane factory photo byBirmingham Museums Trust onUnsplash.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2020-08-12 UTC.