# esp-web-tools and the button that flashes an ESP chip from a web page

> ESP Web Tools is a browser-side toolkit that lets a web page put firmware onto an ESP microcontroller over USB serial, with the chip family and serial type detected automatically. It is small, MIT-style permissive in its Apache-2.0 terms, and constrained by one hard fact: the browser decides how the device is reached, so Web Serial support is the ceiling on what it can do.

**esphome/esp-web-tools** — Open source tools to allow working with ESP devices in the browser

- Repository: https://github.com/esphome/esp-web-tools
- Website: https://esphome.github.io/esp-web-tools/
- Stars: 699 · Forks: 196
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/esphome-esp-web-tools

## Three lines of HTML is the whole integration

ESP Web Tools exists to allow working with ESP devices in the browser, specifically to allow flashing ESPHome or other ESP-based firmwares through the browser, with the board type detected automatically and a supported firmware selected.

The integration is a custom element, and the entire usage in the README is this:

```html
<esp-web-install-button
  manifest="firmware_esphome/manifest.json"
></esp-web-install-button>
```

There is no SDK to call, no JavaScript to write and no build step on the host page. The button takes one meaningful attribute, the manifest path, and the tool does the rest: the user clicks, the browser opens a device picker, the tool works out which ESP chip is on the other end, and it writes the right firmware at the right offsets.

That is the shape of the project. It is a component, not a platform. The package is published as esp-web-tools with the main entry point dist/install-button.js, and it is built with Rollup and TypeScript, with lit for the custom element and @material/web and improv-wifi-serial-sdk among the runtime dependencies.

The audience is a product team shipping a device with a browser-based onboarding flow. If you are building firmware that the end user flashes themselves, this removes the need to distribute a native flasher.

## The manifest is where the chip knowledge lives

The manifest is a JSON document that describes the firmware images and where each one goes in flash memory, and the example in the README is the clearest thing about how the tool works.

The top level carries a name, a version, a home_assistant_domain of esphome and a funding_url. Then a builds array, one entry per chip family, and each entry has a chipFamily and a parts array. Every part is a path and a byte offset:

```json
    {
      "chipFamily": "ESP32-S3",
      "serialType": "uart",
      "parts": [
        { "path": "bootloader_dout_40m.bin", "offset": 4096 },
        { "path": "partitions.bin", "offset": 32768 },
        { "path": "boot_app0.bin", "offset": 57344 },
        { "path": "esp32-s3.bin", "offset": 65536 }
      ]
    },
```

The example covers ESP32, ESP32-C3, ESP32-S2, two ESP32-S3 variants and ESP8266. The bootloader goes at offset 4096 for most families and at 0 for the C3, the partition table at 32768, the boot_app0 partition at 57344, and the application binary at 65536. The ESP8266 entry is different in kind, a single part with the application at offset 0 and no bootloader, because that chip family does not work the same way.

Getting these offsets right is the entire craft of writing a manifest. A wrong offset produces a device that appears to flash and then does not boot, and the tool has no way to tell you that the offset was wrong.

## serialType: the same chip family, two different USB paths

The most interesting field in the manifest is the optional serialType, and the reason it exists is a hardware quirk that would otherwise break the whole flow.

The README explains that the optional serialType field, with the value cdc or uart, lets you ship separate firmware variants for chips that support both native USB CDC, which is built-in USB, and external USB-to-UART bridges. The correct variant is selected automatically based on the detected connection, and builds without a serialType are used as a fallback for any connection type.

This is a case where the manifest format has grown to absorb a real problem. On an ESP32-S3, a board might present itself to the host as a native USB serial device, or it might be behind a CP2102 or similar bridge, and the two cases need different flashing behaviour. The tool detects which one it is looking at and picks the matching build from the same manifest, so the host page does not need to know anything about it.

Note the direction of the fallback. A build with no serialType is used for any connection type, which means the format degrades gracefully. A device family with only one USB path, like most of the older boards, needs no serialType at all.

The two S3 entries in the example are the only ones that carry the field, and they are otherwise identical apart from the binary name, with esp32-s3.bin for the UART build and esp32-s3-cdc.bin for the CDC build, both at offset 65536.

## What actually does the flashing

The dependency list is short and says more than the README does.

esptool-js at version 0.7.0 or later is the component that actually talks to the chip. esptool is the Python tool the ESP ecosystem has used for years, and this is its JavaScript port running in the browser, which is the whole trick: the flashing protocol that normally needs a Python environment now runs in a tab.

improv-wifi-serial-sdk is pinned to exactly 2.8.1, and Improv WiFi is the serial provisioning protocol used to hand a device its WiFi credentials and its configuration over the same serial link. Having it as a dependency suggests the tool can do more than write a binary, though the README does not describe the provisioning flow.

@types/w3c-web-serial in the dev dependencies is the type surface for the Web Serial API, which is the browser feature that makes any of this possible. The type is the API for talking to a serial device from JavaScript, and it is available in Chromium-based browsers. That constraint runs through the entire project: the tool needs Web Serial to reach the device at all.

lit at 3.2.1 or later provides the custom element base class, and @material/web supplies the Material Design components the button and dialog are built from. The build uses Rollup with a set of plugins for TypeScript, Babel, node resolution, JSON, commonjs and terser, so the published artifact is a single minified bundle.

## Running the development server and building the site

The development section is two sentences, and it is enough to get going.

Run script/develop. This starts a server, and you open it at http://localhost:5001. The site itself is index.html and 404.html with the assets in static.

The build script is script/build_website, and it renders those files into website/dist, which is what GitHub Pages serves. There is a detail in the README that is easy to miss and would otherwise waste an hour: that build is also where the analytics snippet in website/plausible-analytics.html gets inlined, so it is not part of the pages you see while developing.

In other words, the local development server serves an un-instrumented copy of the site, and the analytics only appear in the built output. If you are checking what a visitor will actually receive, you need the built version, not the dev server.

The repository layout is consistent with that split: index.html and 404.html at the root, static for assets, src for the TypeScript sources, website for the build output and the analytics template, script for the shell scripts, and a rollup.config.mjs driving the bundle. There is a .devcontainer directory, so a containerised development environment is set up, and a .npmignore, which is what keeps the development tooling out of the published package.

## The version gap and the licence

The repository is not archived, and the last push was on 2026-10-19, so work is current. The tagged releases are 10.4.0 from 2026-07-15, 10.3.0 from 2026-07-13 and 10.2.1 from 2026-01-20.

There is a gap between the release numbering and the package version in the tree. The package.json in the repository says version 0.0.0, and the manifest example in the README carries a version of 2021.10.3 for the ESPHome firmware, which is a different thing entirely, the version of the firmware being flashed rather than the version of the tool. The releases numbered 10.x are the ESPHome version that the tool ships alongside, which is how the project tracks the platform it serves.

The licence is Apache-2.0, stated in the package.json and in the README badge. The README also carries the Open Home Foundation badge, since ESPHome is a project of that foundation.

The version gap between 10.2.1 in January and 10.3.0 in July, followed by 10.4.0 two days later, is the practical signal here. This is a tool that moves in step with the ESPHome release it flashes, so the safe approach is to pin the tool version to the firmware version you ship rather than tracking the default branch.

## Conclusion

ESP Web Tools is a good fit for a product team that wants end users to install a prebuilt firmware on a device they just plugged in, because the install button is a three-line web component and the manifest maps chip families to binary offsets, so no installer application is needed on the user's side. It is a poor fit if you need to flash a device that is not attached over USB serial, or if you need to configure a device rather than replace its firmware, since the manifest describes binaries rather than settings. Before adopting, confirm the target devices expose a serial interface the browser can reach, write a manifest entry with the correct flash offsets for each chip family you ship, and check the esp-web-tools documentation site for the parts of the workflow the README does not cover.

## FAQ

### How do I add the ESP Web Tools install button to a page?

Add the esp-web-install-button custom element with a manifest attribute pointing at your firmware manifest, for example manifest="firmware_esphome/manifest.json". The tool detects the board type and selects the supported firmware automatically.

### What is the serialType field in an ESP Web Tools manifest?

It is an optional field set to cdc or uart that lets you ship separate firmware variants for chips supporting both native USB CDC and external USB-to-UART bridges. The correct variant is selected automatically from the detected connection, and builds without the field are used as a fallback for any connection type.

### How do I run the ESP Web Tools development server?

Run script/develop, which starts a server you open at http://localhost:5001. The build script is script/build_website, which renders index.html and 404.html with the static assets into website/dist for GitHub Pages and inlines the analytics snippet at the same time.

### Which chip families can ESP Web Tools flash?

The example manifest covers ESP32, ESP32-C3, ESP32-S2, two ESP32-S3 variants distinguished by serialType, and ESP8266. Each build entry lists the binary parts and the flash offset for each one.

## Sources

- [esphome/esp-web-tools on GitHub](https://github.com/esphome/esp-web-tools)
- [License: Apache-2.0](https://github.com/esphome/esp-web-tools/blob/main/LICENSE)
- [Project website](https://esphome.github.io/esp-web-tools/)
- [README](https://github.com/esphome/esp-web-tools/blob/main/README.md)
- [Releases](https://github.com/esphome/esp-web-tools/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/esphome-esp-web-tools
