Open-source project
Koenkk/zigbee-OTA avatar
Koenkk/zigbee-OTA

zigbee-OTA, a shared index of Zigbee firmware images and their manifests

A collection of Zigbee OTA files

680 stars335 forksTypeScriptGPL-3.0

At a glance

What is it?
zigbee-OTA is a repository of Zigbee over-the-air firmware images with two JSON manifests, one for current upgrades and one for archived downgrades, plus the automation that validates contributed images. It is aimed at Zigbee tooling maintainers who need a shared catalogue rather than at end users, and its downgrade files come with an explicit warning that a device may refuse them.
Who is it for?
Adopt zigbee-OTA if you build Zigbee firmware distribution or tooling and want a shared, validated catalogue of device images with a machine readable manifest instead of scraping vendor download pages yourself. Do not adopt it as a firmware source for a single device you own, since the repository is a catalogue and index rather than an installer, and do not treat the presence of a downgrade image as a guarantee.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two manifests, one for upgrades and one for downgrades

The repository holds a collection of Zigbee OTA files, and the entry point is a manifest named `index.json` that gives an overview of all available firmware files. A second manifest, `index1.json`, holds firmware files for downgrade, meaning the previous available version before the one listed in `index.json`.

That two-manifest split is the core design decision, and it exists because of how over-the-air updates work in practice. When a newer image is published for a device, the old one is not simply deleted: it is moved into the `images1` directory and recorded in `index1.json`, so a tool that needs to push a device back to a previous version has somewhere to look.

The README is explicit about the limit of that guarantee. A downgrade OTA file may be available for a device through automatic archiving in this repository, but it does not mean the device will actually allow the downgrade, and some devices will refuse the OTA file. The archiving is a convenience the repository can offer; the acceptance decision is made by the device.

For consumers, the practical instruction is that `index.json` is the current catalogue and `index1.json` is a fallback set, and neither is a promise about what a given device will accept.

Contributing an image is a pull request, not a file drop

Adding firmware to the collection is done through a pull request with the image placed in the proper subdirectory under `images`, named for the manufacturer.

The validation is automated. The pull request automation validates the image, and if any error occurs a comment is posted in the pull request. If validation succeeds, a comment is posted that informs of the changes that merging the pull request will commit, in a following commit. The contributor does not edit the manifest by hand.

The rules for contributors are equally explicit. Do not delete, rename or replace current images, and do not submit images in the `images1` directory, because the automation handles archiving as appropriate for each file.

The README documents two routes. The GitHub route is to fork the repository, open the `images` directory and then the manufacturer folder, use the Add file dropdown and Upload files, add the files with a good title and an optional description, pick the option to create a new branch for the commit and start a pull request, and then submit with Propose changes. The console route is the same idea with git commands, and the comment lines in that snippet explain the placeholders, where `username` is your GitHub username for the fork and `manufacturer` is the manufacturer name for the image.

The console workflow for adding a firmware image

The console example is the part most contributors will actually run, and it is short enough to reproduce exactly:

bash
# where `username` is your Github username (to use your fork)
$ git clone --depth 1 https://github.com/username/zigbee-OTA/
$ cd zigbee-OTA
$ git checkout -b my-new-image
# where `manufacturer` is the name of the manufacturer associated with the image (if it does not already exist)
$ mkdir ./images/manufacturer/
$ cp ~/Downloads/my-new-ota.ota ./images/manufacturer/
$ git add .
$ git commit -m "New image for xyz device from abc manufacturer"
$ git push -u origin HEAD

Afterwards you create the pull request from the notification in the repository and wait for the workflow to run the validation.

Two details are worth noting. The clone uses `--depth 1`, so you get only the most recent commit, which is fine for adding a file and avoids pulling the full image history. And the branch is created before the file is copied in, so the push contains only the new image and the commit message follows the pattern the automation expects, naming the device and the manufacturer.

The image itself goes into a directory named after the manufacturer, and the mkdir step is conditional, because the directory may already exist for other devices from the same vendor.

Extra manifest metadata declared in the pull request body

Some images need more than a file. When the image requires extra metadata in the manifest, usually to avoid conflicts or to set restrictions, you declare it in the body of the pull request, in the description field of the first post. The automation looks for valid JSON between a json code fence and will add those fields to the manifest.

The format is a small object:

json
{
    "modelId": "xyzDevice",
    "manufacturerName": ["xyzManufacturer"]
}

There is a specific restriction on how you post it. Do not use code blocks for anything else in the body, to avoid issues, and if you need to add more, add a new comment below, because only the very first post is used for extra metadata detection. If validation failed because of the extra metadata, you can edit the pull request body and the automation re-runs when saved.

The allowed fields fall into two groups. The restriction fields are force, a boolean that ignores `fileVersion` and always presents the image as available, hardwareVersionMax and hardwareVersionMin as numbers, manufacturerName as an array of strings targeting devices with one of those names, maxFileVersion and minFileVersion as numbers, and modelId as a string targeting a model ID. The record fields are originalUrl and releaseNotes, both strings. Any field not on the list is ignored, and any field with the wrong type causes a failure.

The force flag deserves attention because it is the mechanism that overrides the normal version-matching logic, and that is exactly the kind of field a consumer of the manifest should look at first.

One manifest entry per file, enforced by the workflow

The maintainer notes explain the invariants that the workflows enforce, and they are stricter than they first look.

A file name must identify a single image. The workflows refuse to add or archive an image where the target manifest already has a different image, compared by `sha512`, under the same file name, and they refuse pull requests that modify, rename or delete existing images. The one exception is the auto-download path, which may legitimately re-download a newer version under the same file name, since it archives the current file before writing the new one.

There is also a rule for manual manifest edits. If a manual modification of the manifests is necessary, it should be done in a pull request that does not trigger the `update_ota_pr` workflow, which means no changes in the `images/**` directory. As a last resort, the label `ignore-ota-workflow` can be added to prevent the workflow from running.

For a consumer of the manifest, the practical consequences are that a `sha512` identifies an image, a file name identifies a device slot rather than a version, and a manifest entry therefore has to be treated as a current pointer to a specific file.

The metadata shape, and the tooling that maintains the index

The README documents the metadata structure for images, and it separates what the tooling derives automatically from what a contributor adds. The interface has fields parsed from the image itself, such as imageType, fileVersion, manufacturerCode, fileSize and otaHeaderString, and fields derived from the image file in the repository, namely url, sha512 and fileName. On top of those sit the optional extra metadata fields.

The implementation is TypeScript in a pnpm workspace. The package.json declares Node 20 or newer as the engine requirement, pins pnpm as the package manager, and the scripts cover the usual loop: build runs tsc, check runs biome check with a write variant, and the tests run through vitest with a separate coverage configuration. The only runtime dependency is tar, which fits the job, since firmware images are distributed as archives.

The repository layout matches the workflow described above. `images` and `index.json` hold the added upgrade images, while `images1` and `index1.json` hold automatically archived downgrade images, moved there after a merged pull request introduced a newer version or during an auto download. Alongside them are directories for images not in the manifest, retracted images, and a stack info index, plus a `cacerts` directory with a pem file at the root, which is what you would expect from a tool that downloads firmware over HTTPS from vendor sites.

The licence is GPL-3.0, and the package.json states it as GPL-3.0-or-later, which matters if you intend to reuse the tooling itself rather than just the images.

Editorial conclusion

Adopt zigbee-OTA if you build Zigbee firmware distribution or tooling and want a shared, validated catalogue of device images with a machine readable manifest instead of scraping vendor download pages yourself. Do not adopt it as a firmware source for a single device you own, since the repository is a catalogue and index rather than an installer, and do not treat the presence of a downgrade image as a guarantee. Before you rely on it, check whether the device you care about has an entry in index.json at all, read the restrictions fields in the manifest before offering an image to a user, and remember that the last push was on 2026-10-19 with releases published on a roughly weekly cadence, so the index moves underneath you and should be consumed from the manifest rather than from a pinned copy.

Frequently asked questions

What is the difference between index.json and index1.json in zigbee-OTA?

The manifest index.json gives an overview of all available firmware files, while index1.json contains the firmware files for downgrade, meaning the previous available version before the one listed in index.json. A device may still refuse a downgrade image even when one is archived here.

How do I add a new Zigbee OTA image to zigbee-OTA?

Fork the repository, place the image in the images directory under the subdirectory for its manufacturer, commit it on a new branch and open a pull request. The workflow validates the image and posts a comment describing what the merge will commit.

What extra metadata can I declare in a zigbee-OTA pull request?

You can declare force, hardwareVersionMin, hardwareVersionMax, manufacturerName, minFileVersion, maxFileVersion, modelId, originalUrl and releaseNotes as valid JSON in the body of the first post of the pull request. Any field not on the allowed list is ignored, and a field with the wrong type fails validation.

Why was my zigbee-OTA pull request rejected?

The workflows refuse pull requests that modify, rename or delete existing images, and refuse to add an image where the target manifest already holds a different image by sha512 under the same file name. The correct approach is to add the new version so the automation archives the previous one.

What licence applies to zigbee-OTA?

The repository is licensed under GPL-3.0, and the package.json states the licence as GPL-3.0-or-later, with a LICENSE file at the root of the repository.

Official sources

  1. Issues
  2. Koenkk/zigbee-OTA on GitHub
  3. License: GPL-3.0
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/koenkk-zigbee-ota.svg)](https://hysenlabs.com/projects/koenkk-zigbee-ota)