Skip to content

Plugin packages (plugin.json)

Every Mixlar plugin ships as a folder, not a single script. The app reads the folder’s plugin.json manifest before it runs a single line of your code — that’s what makes drop-in installs, hot-loading, and the trust gate possible.

Drop your plugin’s folder into %APPDATA%\Mixlar\config\plugins\:

<PlugName>/
plugin.json ← manifest (required, read before any code runs)
<entry>.py ← the MixlarPlugin subclass(es)
widgets/<id>/ ← device widget bundles (widget.json + assets)
screen/ ← full-screen device page (BETA — reserved, not yet implemented)
icons/ ← PNGs: PC-app UI icons AND device widget image assets

The device widget bundle format (widget.json, elements, the WDATA/WEVENT protocol) is covered separately in Widget spec.

Here’s a complete example, taken from a real single-widget plugin:

{
"manifest": 1,
"id": "mouse_battery",
"name": "Mouse Battery",
"version": "1.2.0",
"author": "Mixlar Labs",
"description": "Shows your wireless mouse battery on the device.",
"entry": "mouse_battery.py",
"icon": "fa5s.mouse",
"icon_color": "#1DB954",
"icons_dir": "icons",
"min_app_version": "1.4.1",
"widgets": [
{ "id": "mouse_batt", "name": "Mouse Battery", "version": "1.2.0" }
],
"screen": { "id": "mouse_screen", "name": "Battery Screen", "beta": true },
"publisher": "",
"sig": ""
}
Field Required Meaning
manifest Yes Manifest format version — must be 1
id Yes Plugin id (should match the .py’s plugin_id)
name Yes Display name
version Yes Semver. The manifest wins over the .py class attributes
entry Yes The plugin .py filename inside the folder — no paths, no ..
author No Shown on the plugin card
description No Shown on the plugin card
icon No Icon shown on the plugin card (e.g. a fa5s.* icon name)
icon_color No Accent color for the icon, as a hex string
icons_dir No Folder of PNGs installed into the app’s icon set (also usable as widget assets)
min_app_version No Reject the package on app versions older than this
widgets[] No Device widgets this plugin ships — see below
screen No BETA placeholder for a future full-screen device page — parsed and shown in the install popup, not yet rendered
publisher No Reserved: signing key id
sig No Reserved: base64 ed25519 signature of the package digest — see Verification & signing

Unknown fields are ignored, so the format is forward-compatible.

Each entry declares a device widget bundle the plugin ships:

{ "id": "mouse_batt", "name": "Mouse Battery", "version": "1.2.0" }
  • id must match a widgets/<id>/ folder, and must be 2–24 characters matching a-z0-9_-.
  • version should equal the "version" field inside that widget’s widget.json. The manifest’s version is authoritative — if the two disagree, mixlar-sdk validate warns but the manifest value wins.
  • Give each widget a real "version": "x.y.z" and bump it on changes — that’s what drives the app’s update prompts on connect.

These are the exact checks the app (and mixlar-sdk validate) run against plugin.json, in order — the first failure blocks the load:

  1. plugin.json must parse as valid JSON and be a JSON object.
  2. manifest must equal 1.
  3. id, name, version, and entry must all be present as non-empty strings.
  4. version must be a valid semver string.
  5. entry must end in .py and must not contain /, \, or ...
  6. The file named by entry must exist inside the package folder.
  7. If min_app_version is set and the running app is older, the package is rejected with a version-mismatch error.
  8. Every entry in widgets[] must have an id matching ^[a-z0-9_-]{2,24}$, and widgets/<id>/widget.json must exist on disk.

mixlar-sdk validate additionally reports (as warnings, not hard failures):

  • id has surrounding whitespace
  • icons_dir is set but the folder doesn’t exist
  • author or description is missing (both are shown on the plugin card)
  • a widget’s manifest version doesn’t match its widget.json version
  • sig is present but fails signature verification (reported as an error, not a warning)
  • Drop-in while the app is running: the folder watcher waits for the copy to finish, runs the trust gate, hot-loads the plugin, and pops up “This plugin supports N widget(s)… Install to device?”Install streams the widget bundle(s) to the device immediately (or on next connect if disconnected); Not now defers, and the widget stays available from the plugin’s Send to Device button.
  • On every connect: the app asks the device for its installed-widget inventory (WLISTWLIST,<id>,<version> lines). Widgets absent from the device auto-install; widgets whose bundled version is newer than the device’s are collected into one Update All / Skip prompt. Skipping is remembered per version, so you’re only asked again when the version increases.
  • Manual: every plugin with widget bundles has a Send to Device button (on the plugin card and in the settings header) that force-pushes its bundles.
  • Old firmware without WLIST falls back to content-hash change detection — everything still installs and updates, just without the version prompt.

Each package gets a deterministic SHA-256 package digest, computed over the manifest (with sig blanked out), the entry file, and the contents of widgets/, screen/, and icons/. The digest is checked against the local trust cache and the Mixlar marketplace API; unknown packages prompt the user before any plugin code runs — the same gate applies to startup loads and to live drop-ins.

publisher and sig are reserved for package signing (ed25519 over the digest). A package with an invalid signature is never auto-trusted. Full key infrastructure is future work — unsigned packages currently use the hash flow above. Details in Signing.

The SDK ships example packages under PC Software/Plugin Examples/:

Example What it shows
HttpTrigger/ Minimal template — macro actions + slider mode, no widget
Mouse Battery/ Single-widget bundle with live data pushes
Custom Widget Demo/ Multi-widget showcase — 5 widgets including interactive elements, pages, and QR codes