Skip to content

Getting a plugin to users

A Mixlar plugin is, at its core, just a folderplugin.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.

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.

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.

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.

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:

Terminal window
mixlar-sdk pack # unsigned .mixplugin next to the package
mixlar-sdk pack --sign my-studio # sign, then pack
mixlar-sdk pack --out dist/my_plugin.mixplugin

A .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.

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.

  1. Manifest read. The app reads plugin.json before running any plugin code. The manifest wins over any conflicting attributes on the .py class itself.

  2. 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.

  3. Hot-load. The plugin is loaded into the running app — no restart.

  4. 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.

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 WLIST support 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.

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:

  1. The local trust cache — packages you’ve already approved.
  2. 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.

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.

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.

  • Packages — the full plugin.json manifest reference and package layout.
  • Signing — key generation, signing, and getting a publisher key pinned.
  • The .mixplugin format — the pack/zip format in detail.
  • Widget spec — the widgets/<id>/ bundle layout referenced by widgets[] in the manifest.