Publishing
How an extension gets into the store — source in, signed package out.
The store keeps one promise: the reviewed source is the shipped package. You submit source, pinned to a commit; the store's own build compiles it, packages it, signs it and publishes it. No binary you built is ever published, so nothing can be swapped after review.
That is also why a package you build yourself installs as not signed: only
the store's build signs, and Lumi says so rather than pretending otherwise.
(A package downloaded from the store and installed from a file keeps its
signature if its .sig file sits beside it, as <package>.sig.)
Submitting
Put the source in a public repository
A Rust crate depending on lumi-extension-api, with manifest.toml beside
its Cargo.toml. Anything under ui/ is packed exactly as it is in the
repository, so commit it as source — see
Building for the store.
Open a pull request against the store
The store lives at github.com/thiennguyen93/lumi-store. The pull request:
- adds your repository as a submodule under
ext/<your-extension-id>, pinned to the exact commit you are submitting, and - adds one entry to
extensions.toml:
[[extension]]
id = "dev.you.thing" # must equal the id in your manifest.toml
path = "ext/dev.you.thing" # the submodule
subdir = "." # where Cargo.toml and manifest.toml live inside itReview
Review is by a person, of the source: the capabilities the manifest declares against what the code actually calls, nothing phoning home, and no misbehaviour shipped as a feature. The pull request's checks also hold the manifest to the rules Lumi's installer applies, so a manifest Lumi would refuse fails there rather than on somebody's Mac.
Merge
Merging is publishing: the store rebuilds every listed extension from source, signs the packages and rewrites the index Lumi reads.
How people find it
Settings → Extensions is the Extension Store. Its Discover tab lists every extension in the store's index, with the featured ones turning over at the top and a filter by category; each has a page of its own — your screenshots, what it adds, what it can reach, the package — with Install on it. The store's page on lumikeys.app lists the same catalogue for people outside Lumi, and its Install button hands the extension to Lumi the same way.
Either way Lumi downloads the signed package and shows the same review sheet a file install gets — this time saying the package came from the store and was verified against its key. What the store page says is browse copy; the sheet reads the verified package. Nothing installs until the person presses the sheet's button — Install, or Continue… when the extension ships an installer of its own.
Two words about your extension are the store's, set on its entry in
extensions.toml in review: category (Productivity, Writing, Windows,
Design, Developer or Utilities), which is the shelf it is filtered under,
and featured = true, which puts it among the plates turning over at the
top of Discover.
Neither is a manifest key, because neither is yours to claim. Pictures for
the page are: see screenshots. So is a
demo video, video — a YouTube link, drawn
ahead of the pictures.
So are the page's words. Put a STORE.md beside your manifest and the page
shows it in place of your one-line description. Lumi draws a small part
of markdown: # headings, paragraphs, - and 1. lists, bold, italic,
inline and fenced code, ---, and links that start with https://, which
open in the browser. Anything else, HTML included, is shown as the
characters it is. UTF-8, at most 16 KB. It is published named by its
content, so an edit reaches the page without a new version. Needs nothing
of Lumi's installer, so it needs no min-lumi-version; a Lumi older than
STORE.md shows the description.
Release notes
Every release needs notes: a CHANGELOG.md beside your manifest, one
## <version> section per release, newest on top.
# Changelog
## [Unreleased]
## [1.2.0] - 2026-09-30
### Added
- Pin the panel so it stays open while you paste.
### Fixed
- Search keeps text cased as it was copied.The newest section must be the version your manifest ships. The headings Keep a
Changelog, release-please, git-cliff and changesets write are all read
(## 1.2.0, ## v1.2.0, ## [1.2.0] - 2026-09-30); the date is optional.
Versions go newest first, each once, and a section holds at most 4 KB of the
same small markdown a store page does. Anything above the first ## and an
Unreleased section are left out.
People read them in two places: the What's new fold on your store page,
before they install, and the Changelog tab on your extension's page in
Lumi after — read from the copy in the package they installed, so it says what
they have, offline included. The store packs the file for you; a package you
build yourself carries it the same way. Rename the tab, hide it or draw it
yourself with [tabs.changelog].
Updates
An update is a pull request that moves your submodule to a new commit and
raises version in the manifest. The id never changes — it is how Lumi
knows the new package replaces the old one.
Raise the version everywhere it is written, in the same commit: the
manifest's is the one Lumi reads, and the store refuses a pull request
while your crate's version in Cargo.toml (a version.workspace = true
included), its row in a committed Cargo.lock, or the version in your
web/package.json names another number. After editing Cargo.toml,
cargo update -p <your crate> --offline brings the lock along.
Lumi reads the store's index at launch, when its window comes forward, and
once an hour in the background, at most every six hours — and whenever the
person presses Check for updates on the Installed tab of Settings →
Extensions, or ⌘R anywhere in that pane. The Updates tab lists what
it found, with Update all. An update is
offered when the store's version is higher than the installed one, compared
as numbers, so 1.10.0 is newer than 1.9.0. Both must be written
major.minor.patch; a version of any other shape is never offered as an
update. An update whose min-lumi-version is above the running Lumi is
listed as needing a newer Lumi.
An update keeps the person's settings, and your
installer is not shown again.
If your extension is switched on, it is told with
Updated, carrying the version it
replaced.
Keep capabilities as narrow in an update as in the first release: a new capability is a new line on the review sheet.