Browser firmware flashing uses the Web Serial API to open a direct byte-stream connection between a webpage and a USB-connected microcontroller. Once you select a serial port in the browser dialog, JavaScript libraries like Espressif’s esptool-js toggle the port’s control lines to reset the chip into bootloader mode and stream compiled binary data directly into its flash memory. You can write new firmware without installing desktop IDEs or Python command-line utilities, while HTTPS and user-permission checks prevent unauthorized hardware access.
How does the Web Serial API connect a webpage to hardware?
The Web Serial API bridges the browser and physical serial ports, or USB and Bluetooth devices that emulate a serial port, according to Mozilla’s 2026 developer documentation. Instead of forcing you to look up operating system port names—such as COM ports on Windows, /dev/ttyUSB on Linux, or /dev/cu.usbmodem on macOS—the browser wraps the connection into a single JavaScript interface.
Websites cannot silently scan or access your connected hardware. Under W3C and browser security rules documented by Mozilla in 2026, the Serial interface only runs in secure contexts (HTTPS) and requires transient user activation. You must physically click a button on the page before the script can call navigator.serial.requestPort() to open the browser’s port selector.
To keep you from picking the wrong peripheral, web flashers pass usbVendorId and usbProductId filters into requestPort(). For example, passing vendor ID 0x10c4 and product ID 0xea60 tells the browser pop-up to display only boards using a Silicon Labs CP210x USB-to-UART bridge.
As of May 2026, Mozilla’s compatibility data shows that the Web Serial API is supported in desktop Google Chrome (version 89+), Microsoft Edge (89+), and Opera (76+), as well as on Android in Chrome for Android (138+), Opera Android (91+), and Samsung Internet (30+). Apple Safari on macOS and iOS and Firefox for Android do not support the API.
How does a browser flasher trigger bootloader mode and write binaries?
Microcontrollers running normal application code ignore incoming firmware writes until they are reset into their serial bootloader. After you approve the port, the web app opens the connection with port.open({ baudRate: 115200 }), configuring the link speed in bits per second alongside UART framing options like 7 or 8 data bits (dataBits), “none”, “even”, or “odd” parity, and 1 or 2 stop bits (stopBits).
To force an ESP32 or ESP8266 into download mode without making you press buttons on the circuit board, the browser toggles two hardware control lines via port.setSignals(): Data Terminal Ready (dataTerminalReady, or DTR) and Request to Send (requestToSend, or RTS). Pulsing DTR and RTS in a timed sequence pulls the microcontroller’s reset and boot pins low, restarting the chip directly into its serial ROM bootloader.
Once the chip responds, Espressif’s esptool-js uploads a small stub program directly into the microcontroller’s RAM. That RAM stub takes over from the slower built-in ROM bootloader to handle high-speed flash operations.
As Google’s Chrome developer documentation (“Read from and write to a serial port”) explains, data flows between the browser and the board asynchronously through the Web Streams API using ReadableStream and WritableStream objects (port.readable and port.writable). Because the streams process Uint8Array byte chunks asynchronously, writing several megabytes of SLIP-framed bootloader packets will not freeze the browser tab.
How to flash firmware in the browser with esptool-js
When you manually flash an ESP32 device, such as updating an Unfolded Circle Dock 3 through Espressif’s hosted esptool-js web utility, the process follows five exact steps:
- Plug the microcontroller into your computer with a USB data cable and open the HTTPS web flasher in desktop Chrome (89+), Edge (89+), or Opera (76+).
- Under the Program section, set the Baudrate dropdown (115200 bps is the standard default for initial connection reliability across USB-serial chips) and click Connect.
- Select your board’s interface in the browser pop-up (such as USB JTAG/serial debug unit or your USB-UART bridge) and click Connect to grant the tab access.
- Set the Flash Address in hexadecimal for each compiled binary file and select the matching file on your computer. In Unfolded Circle’s 2026 support procedure for the Dock 3, you add four separate rows: 0x0 for bootloader.bin, 0x8000 for partition-table.bin, 0x8d000 for ota_data_initial.bin, and 0x90000 for ucd3-firmware.bin.
- Click Program, watch the terminal console verify the erased and written flash blocks, and power-cycle the hardware if it does not automatically reboot into the new firmware.
If your board does not appear in the browser’s port list in step 3, check two things. First, swap out your USB cord to rule out a charge-only cable that lacks data wires. Second, verify that your operating system has Virtual COM Port (VCP) drivers installed for your board’s USB-to-UART bridge chip (such as a WCH CH340/CH341, Silicon Labs CP210x, or FTDI chip), or inspect Chrome’s internal device log by typing about://device-log into the address bar.
What are the common mistakes with browser firmware flashing?
One frequent mistake is accidentally wiping saved device settings, such as Wi-Fi credentials, by choosing the wrong firmware file format. According to Espressif’s esptool-js documentation, flashing a single merged .bin file starting at address 0x0 pads the empty space between partitions with 0xFF bytes. Because writeFlash erases every 4 KiB flash sector from the start of a contiguous image to its end, those 0xFF padding bytes erase the non-volatile storage (NVS) and factory partitions sitting in the gaps.
To update firmware without wiping saved settings, developers either flash only the application partition at its specific offset or use a sparse Intel HEX file parsed with parseIntelHex. Sparse HEX files split the firmware into separate regions and leave the gap sectors untouched when eraseAll: false is set.
Another common failure happens right after clicking Connect or Program, when the console logs the error “Failed to execute ‘setSignals’ on ‘SerialPort'”. This means the browser could not toggle the DTR and RTS lines on the USB-serial chip, usually because another program (such as a serial monitor or open IDE) has locked the port, or the USB bridge driver stalled. Closing other serial apps, unplugging and reconnecting the USB cable, reloading the page, or holding down the physical BOOT button on the microcontroller while plugging it in resolves the lock.
Finally, cranking the baud rate up to maximum speed can cause checksum failures mid-flash on cheaper USB-to-UART bridge chips. If a flash write fails halfway through, drop the baud rate back to 115200 bps and run the write again.

