# esptool-js: flashing Espressif chips from a browser tab

> esptool-js is a TypeScript implementation of the esptool serial flasher that runs in the browser over the Web Serial API, so an ESP32 can be programmed without installing Python on the machine. It covers flashing, reading and erasing flash, and it deliberately omits ELF to binary conversion and the companion tools of the original.

**espressif/esptool-js** — Javascript implementation of flasher tool for Espressif chips, running in web browser using WebSerial.

- Repository: https://github.com/espressif/esptool-js
- Website: https://espressif.github.io/esptool-js/
- Stars: 547 · Forks: 184
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/espressif-esptool-js

## The same flasher, without the Python

This repository contains a JavaScript implementation of esptool, the serial flasher utility for Espressif chips, and the key implementation detail is that it is based on the Web Serial API and works in Google Chrome and Microsoft Edge version 89 or later, as well as Google Chrome on Android version 61 or later through the web-serial-polyfill compatibility layer. That constraint shapes everything. A browser talking to a serial port is a different model from a Python process doing it, and the Web Serial API is built around the user granting access to a specific port through a prompt, which is why the basic setup in the README starts by asking the browser for a port rather than opening a device by name. The project is written in TypeScript, published under Apache-2.0, and the primary language of the repository is TypeScript. The release history is regular, with v0.7.0 on 2026-09-21, v0.6.1 on 2026-08-06 and v0.6.0 on 2026-03-26, and the last push to the repository was on 2026-09-21, the same day as the newest tag. The homepage hosts the documentation and the API reference.

## What the JavaScript port leaves out, and why it matters

There is a note in the README that anyone evaluating this needs to read before anything else. Unlike the Python-based esptool, esptool-js does not implement generation of binary images out of ELF files, and does not include companion tools similar to espefuse.py and espsecure.py. The first of those is the significant one, because ELF to binary conversion is where a linker-produced artefact becomes something the flasher can write, and a C or Rust project produces an ELF. So the practical workflow is that you build your firmware with the normal toolchain, convert it to a binary image with the standard Espressif tooling, and then flash that image from the browser. The companion tools are a different matter, since espefuse.py and espsecure.py handle fuse burning and secure boot encryption, which are operations you would rarely want to do from a web page anyway, where a mis-click has consequences that are hard to undo. The scope the port does cover is the everyday work, which is what makes a browser flasher useful rather than a curiosity.

## Three ways to load it, and the import map problem

The installation section offers a CDN route, an npm route and a yarn route, and the CDN route has a wrinkle worth understanding. The single-file browser load uses the bundle at unpkg, and the README explains that dependencies such as pako are inlined, so no import map is required. The unbundled library entry is for Node and bundlers, and a browser loading lib/ as native ESM must map bare specifiers, which the README shows with an import map.

```html
<script type="importmap">
  {
    "imports": {
      "pako": "https://unpkg.com/pako@2.1.0/dist/pako.esm.mjs",
      "atob-lite": "https://unpkg.com/atob-lite@2.0.0/atob-browser.js"
    }
  }
</script>
```

The two mapped specifiers are the whole runtime dependency list, since the package manifest lists exactly two dependencies, atob-lite and pako, which is pako doing the compression. The npm install is one command.

```bash
npm install --save esptool-js
```

And the yarn route is the equivalent.

```bash
yarn add --save esptool-js
```

Nightly builds are published from a nightly.link workflow link. The manifest shows the packaging, with the module and main both pointing at lib/index.js, types at lib/index.d.ts, and the files array shipping lib and bundle.js, and the build script running a clean, then tsc, then rollup with a config.

## The API is four objects and a loader

The basic setup imports a specific set of names from the package.

```typescript
import {
  ESPLoader,
  Transport,
  LoaderOptions,
  FlashOptions,
  FlashModeValues,
  FlashFreqValues,
  FlashSizeValues,
  IEspLoaderTerminal,
} from "esptool-js";
```

The workflow has seven steps, and the first is port access, where the browser prompts the user to select a device, and you can narrow the prompt with USB vendor and product identifiers, the example using the Silicon Labs CP210x as a filter.

```typescript
// Request port access (user will be prompted to select a device)
const port = await navigator.serial.requestPort();
```

You then build a Transport over that port, optionally create a terminal interface for logging, assemble the loader options with a baud rate of 115200, and construct the ESPLoader.

```typescript
// Create transport instance
const transport = new Transport(port, true);
```

Connecting and detecting the chip is one call, and the README notes that this will reset the device.

```typescript
try {
  // Connect and detect chip (this will reset the device)
  const chipName = await esploader.main();
  console.log(`Connected to: ${chipName}`);
} catch (error) {
  console.error("Failed to connect:", error);
}
```

## Flashing, reading and erasing flash

The flash step takes an array of file segments, each with its data and its target address, plus the physical parameters of the chip. The flash mode is one of qio, qout, dio or dout, the frequency one of 80m, 40m, 26m, 20m and so on, and the size one of 256KB, 512KB, 1MB, 2MB, 4MB and so on. There is an option to erase the entire flash before writing, an option to compress the data during transfer, a progress callback that receives the file index, the bytes written and the total, and an optional MD5 hash callback for verification, which the README says you implement yourself. Writing is one call and the optional reset afterwards is another.

```typescript
// Flash the firmware
await esploader.writeFlash(flashOptions);

// Reset the device after flashing (optional)
await esploader.after("hard_reset");
```

Reading flash is the mirror operation, with a start address, a size and a progress callback.

```typescript
const startAddress = 0x1000;
const size = 4096; // Read 4KB
```

Erasing is either the whole chip or a region, with the region variant commented out in the example as not always supported.

```typescript
// Erase entire flash
await esploader.eraseFlash();

// Or erase a specific region (if supported)
// await esploader.eraseRegion(startAddress, size);
```

## Custom reset strategies, and the DTR/RTS timing problem

The advanced feature is custom reset strategies, and the README is careful to mark it as optional. By default the ESPLoader uses built-in reset strategies that work with most ESP32 and ESP8266 boards, and you only need your own if you are on custom hardware requiring specific DTR and RTS sequences, if the defaults do not work with your board or adapter, if you want fine-grained control over reset timing, or if you are on a non-standard development board. The reset vocabulary is three commands, D for setDTR, R for setRTS and W for a wait in milliseconds, with commands separated by a pipe character, and the worked example D0|R1|W100|D1|R0|W50|D0 sets DTR false, sets RTS true, waits 100 milliseconds, sets DTR true, sets RTS false, waits 50 milliseconds and sets DTR false. The implementation imports the built-in strategies alongside the custom one, with ClassicReset, HardReset, UsbJtagSerialReset, CustomReset, ResetConstructors and a validation helper. The reason the timing matters is that the classic way of putting an ESP into its bootloader is to pulse DTR and RTS, and the sequence has to be timed correctly against the chip's own reset timing, which is a detail that varies between boards and adapters.

## Where esptool-js is the wrong choice

The first limit is the missing ELF conversion, which means a browser flasher cannot be the only tool in a C or Rust workflow, and that is not a small gap, since producing the binary image is the step the compiler toolchain does for you. The second is the browser platform constraint, since the Web Serial API is available in Chrome and Edge and, through a compatibility layer, in Chrome on Android, and a team whose users are on Safari or Firefox has a different story. The third is the security and supply chain shape of a browser flasher, since a page that can talk to a serial port is a page that can flash a chip, and the user has to decide whether they trust the page origin, and a self-hosted copy of the bundle is a reasonable mitigation that the CDN route makes easy and the npm route less so. The fourth is that a browser flasher is convenient for a person and awkward for a pipeline, so flashing boards in manufacturing still wants the Python tool. And the fifth is the risk of a mis-click, since erasing flash and writing a wrong image are both one call away, and the erase option defaults to false and the reset afterwards is optional, so the defaults are conservative, but the tool does not add a confirmation step of its own.

## Conclusion

Adopt esptool-js if you flash Espressif chips from a browser and want a workflow that needs no Python install or command line, and you are willing to work in the Web Serial API's model of user-granted port access. Skip it if you need the parts the JavaScript port leaves out, namely generating binary images from ELF files or the companion tooling of the original esptool. Verify first that your browser is one of the supported ones, that your USB adapter appears as a serial device to the browser rather than as a raw USB device, and that you have a pre-built binary image since the ELF conversion is not implemented here.

## FAQ

### What is esptool-js and which browsers support it?

It is a JavaScript and TypeScript implementation of esptool, the serial flasher for Espressif chips, based on the Web Serial API. It works in Google Chrome and Microsoft Edge version 89 or later, and in Google Chrome on Android version 61 or later through the web-serial-polyfill compatibility layer. The project is Apache-2.0 licensed and published as esptool-js on npm.

### How do I load esptool-js in a browser?

For a single file browser load, use the bundle at unpkg, where dependencies such as pako are inlined so no import map is required. A browser loading lib/ as native ESM must map bare specifiers with an import map, and for Node and bundlers you can install the package with npm install --save esptool-js or yarn add --save esptool-js.

### What can esptool-js do and what does it leave out compared to the Python esptool?

It can request serial port access, connect and detect the chip, flash firmware with configurable mode, frequency, size, compression and progress reporting, read flash memory, erase flash or a region, and reset the device, including custom DTR and RTS reset strategies. Unlike the Python-based esptool it does not implement generation of binary images out of ELF files and does not include companion tools similar to espefuse.py and espsecure.py.

## Sources

- [espressif/esptool-js on GitHub](https://github.com/espressif/esptool-js)
- [License: Apache-2.0](https://github.com/espressif/esptool-js/blob/main/LICENSE)
- [Project website](https://espressif.github.io/esptool-js/)
- [README](https://github.com/espressif/esptool-js/blob/main/README.md)
- [Releases](https://github.com/espressif/esptool-js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/espressif-esptool-js
