wled's 17 LED outputs, its EUPL copyleft, and a Python toolchain owned by PlatformIO
Control WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi!
At a glance
- What is it?
- WLED is an ESP32 firmware for addressable LEDs, EUPL-1.2 licensed, with 200 and more effects, segments, 17 parallel outputs, a JSON API and integrations for DMX, MQTT, Hue and Home Assistant. The repository is not a single project: a PlatformIO C++ build, a Node asset pipeline that assembles the web interface, and a Python dependency set that is entirely PlatformIO's own, pinned by pip-compile.
- Who is it for?
- WLED fits someone building addressable LED art, a matrix panel or a lighting installation who wants a firmware with a documented JSON API and a large community usermod library rather than a bare microcontroller project. It does not fit an ESP8266 project, since the hardware list is ESP32 only across four variants, and it does not suit someone who needs an MIT or permissive licence on reflashed firmware, since EUPL-1.2 is copyleft.
- Can I use it commercially?
- Yes, with conditions. EUPL-1.2 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- wled/WLED19,045 ★ · +347
| Repository | 2026-09-21 | 2026-11-14 |
|---|---|---|
| wled/WLED | 18.7k | 19k |
347 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The package.json builds the web interface, not the firmware
The root Node package is not the product. It is named `wled`, described as Tools for WLED project, and its `main` points at a build script:
node tools/cdata.jsSo `npm run build` assembles the firmware's web interface. The four dependencies say what assembling means: `html-minifier-terser` to minify markup, `clean-css` to minify styles, `web-resource-inliner` to inline assets, and `nodemon` to watch for changes. There is no framework, no bundler and no runtime library. The built-in web UI is a set of static files that get inlined and minified, which is the right approach for something with a fixed memory budget on a microcontroller.
The development script shows where the inputs live:
nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.jsIt watches `tools/` and `wled00/data/`, and the extension list includes images, so a new palette or a changed icon triggers a rebuild of the interface.
The firmware itself is C++ and is not built by any of this. It is built with PlatformIO, from `platformio.ini`, with board definitions in `boards/` and local overrides in `platformio_override.sample.ini`. So there are two toolchains in this repository and they do not overlap, and the Node one needs version 20 or newer per the engines field.
Every Python dependency belongs to PlatformIO and is pinned by pip-compile
The `requirements.txt` opens with a header that says it is autogenerated by pip-compile with Python 3.13, and the command is named. Below that, every entry is pinned with `==` and followed by a comment saying what it arrived through.
Read the provenance comments rather than the names. `platformio==6.1.19` is the only direct requirement, coming from `requirements.in`. Everything else arrives through it: `bottle==0.13.4` via platformio, `starlette==0.46.2` via platformio, `uvicorn==0.34.3` via platformio, `wsproto==1.2.0` via platformio, `marshmallow==3.26.2` via platformio, `ajsonrpc==1.2.0` via platformio, plus `semantic-version`, `tabulate`, `colorama`, `pyserial`, `pyelftools` and the requests stack.
So the Python layer of this project is a solved set rather than a chosen one, and it happens to contain two web frameworks because the build tool drags them in. That is a normal consequence of pinning a toolchain, and it has one consequence for a security-minded reader: there is no way to take a Python dependency out of this file without changing what the build tool needs, and the lock is regenerated rather than edited.
The pairing with `requirements.in` is the mechanism. You edit the input, you run pip-compile, and the output is the exact set above.
Seventeen outputs, four ESP32 variants, and no ESP8266
The hardware section is where compatibility questions get answered, and it splits sharply between the microcontroller and the LEDs.
On the MCU, the list is ESP32 and only ESP32, with the original, S2, S3 and C3 variants named. The consequence is that a search for this firmware on an ESP8266 lands somewhere it will not run, because the name is generic enough to attract searches for a WiFi module that is not in the list.
On the LED side the list is long and specific: WS2812B, WS2811, WS2815, SK6812, WS2805, TM1914, APA102, WS2801, LPD8806 and more, plus RGBW, RGB+CCT and white-only strips, and PWM outputs for analogue LEDs and dimmers. Panels are covered too, with 2D LED matrix support and HUB75 RGB matrix panel support on ESP32.
The number worth knowing is the output count. Up to 17 LED outputs on ESP32, achieved with parallel I2S and RMT, which is the technique that makes a single microcontroller drive many strips without a second chip. Ethernet is supported for a range of boards from QuinLED, LILYGO and Olimex, and firmware updates are over the air with both HTTP and ArduinoOTA, password-protectable.
A per-output brightness limiter, and a warning that names three effect modes
Two safety features sit in different parts of the feature list, and both are concrete rather than general.
The first is under segments and control: a configurable Auto Brightness Limiter, per output, described as being for safe operation. Per output is the operative word. A lighting project that can drive seventeen strips can push a lot of current through one of them, and a limiter that applies per output rather than globally means one misconfigured strip does not drag the rest down or leave the whole install unprotected.
The second is at the very bottom of the README, under a disclaimer. If you are prone to photosensitive epilepsy, the recommendation is not to use this software. If you still want to try, avoid strobe, lightning or noise modes and high effect speed settings. So three effect families and a speed setting are named specifically, which is more useful than a general warning because a user can act on it.
The disclaimer closes by noting that, as per the EUPL licence, no liability is assumed for any damage to you or any other person or equipment. That is a copyleft licence's liability position stated in the project's own words, on a project whose entire purpose is emitting light at people.
EUPL-1.2 is copyleft, and the root Node package says ISC
There are two licence statements in this repository and they are not about the same thing.
The project's licence is the EUPL v1.2, stated in the developer-friendly feature list as a bullet, in the Other section, and in a licence file linked from the README. The EUPL is a European Union copyleft licence, which puts it in the same family as the GPL rather than with MIT or BSD. The disclaimer leans on it, saying that as per the EUPL licence no liability is assumed.
The root `package.json`, for the Node tooling, says:
"license": "ISC",That is not a contradiction, because the two files cover different things, the tooling is not the firmware. It is still the kind of detail that trips a scanner, and it means a repository-wide licence check has to know that only the firmware is EUPL.
For an adopter the practical question is about reflashing. If you take the firmware, modify it and put it on your own board, you are in copyleft territory and the terms travel with the binary. If you use the firmware as shipped, none of that applies.
A third-party CORS proxy sits in the path of a browser-driven API
Two lines in the Other section deserve a second look. The first is a credit: CORS proxy by Corsfix, linking to corsfix.com.
A screen controller's web interface is reached by a browser, and a browser will not let a page on one origin talk to a device on another. That is the same-origin policy, and a CORS proxy is one of the ways around it. The README does not say which part of the project uses it.
The second line is the whole integration list, and it is long because the JSON API is the contract. There are JSON and HTTP request APIs, MQTT with Home Assistant discovery, E1.31 and Art-Net and DDP and TPM2.net for DMX and professional lighting, UDP realtime sync across multiple devices, Philips Hue sync, diyHue and Hyperion integration, Adalight and TPM2 over serial for PC ambilight, infrared remote control with a 24-key RGB receiver, Alexa voice control, and timers with NTP time sync including full timezone and DST support.
That is a well-built API surface, and it is also the reason to read the CORS credit. Anything that automates your lights ends up speaking this API from somewhere, and where the cross-origin hop is routed is worth knowing before you put a venue's lighting on it.
An AGENTS.md and a stated position on AI-assisted contributions
The contributing section is short and has two paragraphs that say something unusual. The first asks you to skim `CONTRIBUTING.md` first, noting that it covers how the project likes pull requests and issues to look, including their take on AI-assisted contributions. The second is addressed to a category of contributor rather than a tool: if you are an AI coding agent, `AGENTS.md` is for you, and you should read it before modifying any files.
So machine-authored contributions are anticipated, and there is a document governing them. That is a different posture from most firmware projects, which either say nothing or discourage generated patches.
The repository is consistent with that. There is a `CONTRIBUTING.md`, a `CODE_OF_CONDUCT.md`, an `AGENTS.md`, a `.coderabbit.yaml` for an automated reviewer, and three definitions of a reproducible development environment: a `.devcontainer/` directory, a `.gitpod.yml` and a `.gitpod.Dockerfile`.
The formatting side is conventional and visible too, with a `.clang-format` at the root. C++ style is therefore machine-checkable, which is the other half of making machine contributions workable: a policy for what to send, and a formatter for how it should look.
The README links wled-dev while the repository is wled, and main is on 17.0.0
Three facts about versioning that a reader pinning a firmware image needs.
First, the links and the repository do not agree. The badge row and the release list point at `github.com/wled-dev/WLED`, the licence link is `raw.githubusercontent.com/wled-dev/WLED/main/LICENSE`, and the Gitpod URL is `https://gitpod.io/#https://github.com/wled-dev/WLED`. The `package.json` repository field says `git+https://github.com/wled/WLED.git`, and that is where this file lives. So the README and package.json name different organisations for the same repository.
Second, nightlies are releases. The three most recent tags are a tag literally named `nightly`, titled Nightly Release 20260717, then v16.0.1 from 2026-07-07, and v16.0.0 from 2026-05-03. So there is a published nightly channel and a stable 16.0.x channel, and the nightly is a release rather than a build artefact you have to find in CI.
Third, the default branch is already in the next major. The Node package version reads `17.0.0-devV5`, five development builds into 17.0.0, while the newest stable tag is 16.0.1 and the last push to the default branch was on 2026-11-01. If you are deciding what to flash, the stable tag is the one with a version number you can point at, and the branch is ahead of it.
Editorial conclusion
WLED fits someone building addressable LED art, a matrix panel or a lighting installation who wants a firmware with a documented JSON API and a large community usermod library rather than a bare microcontroller project. It does not fit an ESP8266 project, since the hardware list is ESP32 only across four variants, and it does not suit someone who needs an MIT or permissive licence on reflashed firmware, since EUPL-1.2 is copyleft. Before adopting, check three things: which ESP32 variant your board is, since S2, S3 and C3 are named separately and the 17-output capability is an ESP32 feature, whether your strip type is on the list, from WS2812B through APA102 and LPD8806 to RGBW, RGB+CCT and white-only, and the photosensitivity warning, which tells you to avoid strobe, lightning and noise modes and high effect speed.
Frequently asked questions
What is WLED compatible with?
On the microcontroller side it is ESP32 only, with the original, S2, S3 and C3 variants named. On the LED side it lists WS2812B, WS2811, WS2815, SK6812, WS2805, TM1914, APA102, WS2801 and LPD8806, plus RGBW, RGB+CCT and white-only strips, PWM outputs for analogue LEDs and dimmers, HUB75 RGB matrix panels and 2D LED matrices on ESP32, and Ethernet boards from QuinLED, LILYGO and Olimex.
How do I connect to my WLED?
Over WiFi, with Multi-WiFi support for connecting to up to 3 networks and automatic access point fallback, and the WLED app for Android and iOS. For automation there are JSON and HTTP request APIs, MQTT with Home Assistant discovery, UDP realtime sync across devices, and ESP-NOW for device-to-device sync with no WiFi router required.
Does WLED run on an ESP8266?
The hardware support section names ESP32 and lists the original, S2, S3 and C3 variants, with no ESP8266 among them. It states up to 17 LED outputs on ESP32 using parallel I2S and RMT, and Ethernet support for a range of boards including QuinLED, LILYGO and Olimex.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wled-wled)Star history chart for your README
[](https://hysenlabs.com/projects/wled-wled)Was this useful?