Getting a plugin to users
A Mixlar plugin is, at its core, just a folder — plugin.json plus an entry
script and optional widgets/, icons/, and screen/. Everything about
distribution is really about getting that folder (or a zipped version of it)
in front of the Mixlar app and letting it take over from there. This page
walks through the three ways a plugin reaches a user, what happens the moment
it lands, and how the app decides whether to trust it.
The three delivery paths
Section titled “The three delivery paths”| Path | Who does it | Best for |
|---|---|---|
| Drop-in folder | You (or the user) copies the plugin folder into the plugins directory | Local development, quick iteration |
| Send to app | The Mixlar Studio pushes the folder for you | Testing a plugin you’re actively building in the Studio |
.mixplugin file |
You pack, optionally sign, and hand the user one file | Sharing a finished plugin with someone else |
All three paths land in the same place — the app’s plugins directory — and go through the same install and trust logic once they’re there.
Drop-in folder
Section titled “Drop-in folder”Every plugin folder lives under:
%APPDATA%\Mixlar\config\plugins\Drop a plugin folder in there while the app is running and a folder watcher picks it up: it waits for the copy to finish, runs the trust gate, hot-loads the plugin, and shows an install popup — “This plugin supports N widget(s)… Install to device?” No restart required.
Send to app (from the Studio)
Section titled “Send to app (from the Studio)”While you’re iterating on a plugin in the Mixlar Studio, Send to app copies your working folder into the plugins directory for you — the same drop-in flow above, just without a manual file copy. This is the fastest loop for local development: edit, send, watch the install popup, done.
Packaged .mixplugin
Section titled “Packaged .mixplugin”For handing a plugin to someone else — a teammate, a marketplace listing, a
download link — pack it into a single .mixplugin file with the
mixlar-sdk CLI:
mixlar-sdk pack # unsigned .mixplugin next to the packagemixlar-sdk pack --sign my-studio # sign, then packmixlar-sdk pack --out dist/my_plugin.mixpluginA .mixplugin is a plain zip of the package folder, with the folder itself
as the top-level entry — no compression trickery, no embedded code execution.
Extracting it next to a destination reproduces the package unchanged.
The install side is just as simple: mixlar.packaging.install() unpacks a
.mixplugin straight into the app’s default plugins directory
(%APPDATA%\Mixlar\config\plugins), with path-traversal guards applied
before anything touches disk — absolute paths, .. segments, and
multi-top-level-folder zips are all rejected.
What happens once it lands
Section titled “What happens once it lands”Once a plugin folder exists under %APPDATA%\Mixlar\config\plugins\ —
however it got there — the app treats it the same way, whether it was loaded
at startup or dropped in live.
-
Manifest read. The app reads
plugin.jsonbefore running any plugin code. The manifest wins over any conflicting attributes on the.pyclass itself. -
Trust gate. The package digest is checked against the local trust cache and the Mixlar marketplace API (see below). Unknown packages prompt the user before any plugin code runs.
-
Hot-load. The plugin is loaded into the running app — no restart.
-
Widget install prompt. If the manifest declares
widgets[], the app pops up “This plugin supports N widget(s)… Install to device?” Install streams the widget bundle(s) to the connected device immediately, or on next connect if it’s disconnected. Not now defers — the widget stays available later from the plugin’s Send to Device button.
On-connect install and update
Section titled “On-connect install and update”Every time a device connects, the app asks it for its installed-widget
inventory over a WLIST request, getting back WLIST,<id>,<version> lines
per widget already on the device. From there:
- Widgets absent from the device auto-install.
- Widgets whose bundled version is newer than what’s on the device are batched into a single Update All / Skip prompt — you don’t get nagged once per widget.
- Skipping is remembered per version; you’re asked again only once the bundled version increases.
- Firmware without
WLISTsupport falls back to content-hash change detection — everything still installs and updates correctly, just without the version prompt.
This is why giving each widget a "version": "x.y.z" in its widget.json,
mirrored in the plugin manifest’s widgets[] entry, matters: that field is
what drives the whole update-prompt machinery. See
Widget spec for the widget bundle layout.
Outside of the connect flow, every plugin with widget bundles also gets a Send to Device button (on the plugin card and in the settings header) that force-pushes its bundles on demand.
The trust gate
Section titled “The trust gate”Before any plugin code runs — at startup or on a live drop-in — the app
computes a deterministic package digest: a SHA-256 over plugin.json
(with "sig" blanked and keys sorted), the manifest’s entry .py file, and
every allow-listed file under widgets/, screen/, and icons/.
That digest is checked against two things:
- The local trust cache — packages you’ve already approved.
- The Mixlar marketplace API — packages published there.
Unknown packages prompt the user before anything executes. This is the baseline flow, and it’s all a package needs — signing is optional groundwork on top of it.
Optional signing
Section titled “Optional signing”The manifest’s publisher/sig fields carry an ed25519 signature of the
digest. Verifying a package resolves to one of three states:
| Result | Meaning |
|---|---|
| Unsigned | "sig" is empty — falls through to the hash/marketplace flow above |
| Valid | Signed, and the signature verifies against a pinned publisher key |
| Invalid | Signed but broken — unknown publisher id, or a tampered/mismatched package |
Only publishers pinned in both the app and the SDK are auto-trusted. An unpinned key always verifies as invalid, even if the signature itself is cryptographically correct — that’s intentional, not a bug. A package with an invalid signature is never auto-trusted.
Full details on generating keys, signing, verifying, and getting a third-party publisher key pinned live in Signing.
Which path should you use?
Section titled “Which path should you use?”Use drop-in or Send to app from the Studio. Both hot-load instantly and you get the install/update prompts as you’d see them in production — no packaging step needed while you’re still changing code.
Pack a .mixplugin with mixlar-sdk pack, optionally --sign it if you
have a pinned publisher key, and hand over the single file. They install it
with mixlar.packaging.install() or by dropping the extracted folder in
directly.
Pack and sign the same way; the marketplace API is one of the two places the trust gate checks a package digest against, alongside the local trust cache.
Next steps
Section titled “Next steps”- Packages — the full
plugin.jsonmanifest reference and package layout. - Signing — key generation, signing, and getting a publisher key pinned.
- The
.mixpluginformat — the pack/zip format in detail. - Widget spec — the
widgets/<id>/bundle layout referenced bywidgets[]in the manifest.