Skip to content
led-ticker

Plugins

Plugins extend led-ticker without forking core. A plugin is a small Python package that registers extra widgets, transitions, color providers, animations, borders, easings, emojis, fonts, or lifecycle hooks through led-ticker’s public plugin API. The flagship example is the pool water-temperature widget, which lives in the led-ticker-plugins monorepo and contributes type = "pool.monitor".

You don’t need any plugins to run led-ticker — the built-in widgets cover most signs. Reach for a plugin when you want something they don’t offer (pool temperatures, a smart-home sensor, a custom feed), and anyone can publish one without changes to core.

Plugins can connect anything with an API — sensor values, custom data feeds, brand fonts, lifecycle hooks. The pool widget shows the pattern: an InfluxDB water-temperature query becomes a rotating display widget wired up in a single register(api) call.

Docker (the primary path) — edit config/requirements-plugins.txt and restart:

Terminal window
# First time: create your plugin manifest from the example
cp config/requirements-plugins.example.txt config/requirements-plugins.txt
# Add the plugin name, then:
docker compose restart

requirements-plugins.txt is a standard pip requirements file — one package per line. The startup reconcile installs everything listed and removes anything that was removed. Add a plugin by appending its name; remove it by deleting the line. No image rebuild needed.

Zero-terminal alternative: if you have the web status UI running, the Store tab offers a point-and-click install — see Install from the web UI below.

Dev / source checkout convenience — if you also have a local dev checkout (make dev), the led-ticker plugin CLI subcommands edit the manifest for you:

Terminal window
led-ticker plugin add pool # + line in config/requirements-plugins.txt (no install)
led-ticker plugin remove pool # − line
docker compose restart # installs/uninstalls on the next start
led-ticker plugin install pool # add + pip install now (dev virtualenv only)
led-ticker plugin uninstall pool # remove + pip uninstall

All subcommands resolve a catalog name to a pip source (git or PyPI), or take a raw pip spec (git+https://… / a PyPI requirement) like pip itself. Run from your project root so the manifest lands in config/requirements-plugins.txt (the file the Docker build and startup reconcile read); pass --config <path> for a non-standard layout. See the CLI reference for all flags. led-ticker plugin list marks each plugin [declared] (in the manifest) and [installed] (in this environment), so you can confirm what the next restart will include.

  • On Docker, plugins install at container startup into a persistent ticker-plugins volume (separate from the image layer) — the image itself never changes. Installs are constrained to core’s dependency versions (a plugin can bring its own new libraries but can’t move a version core pins — a conflict is caught on start and logged, never breaks silently at runtime).
  • The live config/requirements-plugins.txt is gitignored — it’s yours to customize per sign. A fresh clone installs no plugins until you create it.

Once installed, a plugin registers automatically via its led_ticker.plugins entry point — its widget types are available immediately at startup.

If you have the web status UI running, the Store tab offers a point-and-click alternative for catalog plugins. Open the dashboard, click Store, find the plugin you want, and click Install. The Store writes config/requirements-plugins.txt for you.

A token is required to install or remove. Browsing the list is open, but clicking Install or Remove requires a valid token — set LED_TICKER_WEB_TOKEN in your .env (see the web UI auth docs). Without a token the buttons are disabled.

Changes take effect on the next docker compose restart — same as editing the manifest by hand. Once you click Install, the Store shows a “pending restart” banner with a copyable restart command. Run it from your project root when you are ready:

Terminal window
docker compose restart

Removal is blocked while your config still uses the plugin. If config.toml still references any of the plugin’s widget or transition types when you click Remove, the Store tells you which sections they appear in and links to the Config editor to remove them first. Once the config is clean, click Remove again and restart.

Catalog plugins only. The Store lists only verified catalog plugins from the led-ticker-plugins monorepo. To install a community plugin or a plugin you are developing locally, add it to config/requirements-plugins.txt directly (see the CLI section above).

Editing an exact PyPI pin (led-ticker-pool==0.1.0==0.2.0) and restarting is enough: the reconcile compares the manifest pin against the installed version and reinstalls in place when they differ.

Changing an unpinned line, a range spec (>=, ~=), or a git/URL source (e.g. retargeting @main or a moved branch) is not reliably detected on a restart — the namespace is unchanged and the reconcile can’t tell whether the upstream source moved, so it leaves the installed copy alone (and logs an INFO saying so). To force a refresh of a non-pinned source, reset the plugin volume (see reset all plugins below). Pinning to a tag or commit SHA avoids this entirely and is recommended for production signs.

For a production sign, pin every plugin to an immutable tag or commit SHA — not @main, a moving branch, or a floating range. A moving source is not re-detected on restart (see Changing a plugin’s version or source above), and an immutable pin makes each deploy reproducible. For example:

# config/requirements-plugins.txt (production example)
led-ticker-weather==0.3.0
led-ticker-rss==0.2.1

Your config/requirements-plugins.txt and config.toml are gitignored and per-sign — upstream changes never rewrite them for you. When a plugin’s catalog name, package, or a widget/transition type changes (a pack consolidation, a rename), reconcile both files on each sign at deploy: the manifest line, and any affected type = "…" in the config. Run led-ticker validate config.toml to catch a stale type before it reaches the panel.

Delete the line from config/requirements-plugins.txt and restart:

Terminal window
led-ticker plugin remove pool
docker compose restart

The container uninstalls the plugin from the ticker-plugins volume on the next start. The manifest is authoritative — anything not listed is removed.

To reset all plugins (reinstall everything from scratch on the next start):

Terminal window
docker compose down -v
docker compose up -d

docker compose down -v stops the stack and removes the project’s named volumes by their real names (e.g. led-ticker_ticker-plugins). Running docker volume rm ticker-plugins with the bare name will silently fail — Compose prefixes named volumes with the project name. This is also the fix if a plugin volume becomes corrupt or out of sync.

The first docker compose restart after adding a plugin needs network access to download it. If your sign runs without internet access, either:

  • Pre-seed the volume by running docker compose restart once on a networked machine (or host), then moving the volume data to the air-gapped sign.
  • Build a derivative image that bakes the plugin in: write a small Dockerfile that starts FROM led-ticker and adds a RUN pip install -c /code/constraints-core.txt <plugin> layer with your plugins, build it on a machine with network access, then deploy that image to the air-gapped sign. The -c /code/constraints-core.txt flag pins the install to core’s frozen dependency versions, so a baked plugin can’t bump a core-pinned dependency (the same constraint the runtime reconcile applies). One caveat: a PEP 517 build from an sdist runs in an isolated build environment that does not inherit -c, so the constraint only governs the final install resolution — prefer prebuilt wheels and pin to vetted, immutable sources (a tag or commit SHA, not @main).

Relationship to the [plugins] config block

Section titled “Relationship to the [plugins] config block”

These are complementary layers:

  • config/requirements-plugins.txt controls what is installed (applied at container startup).
  • The optional [plugins] block in config.toml controls what is loaded: enabled toggles plugin loading, disable = ["namespace"] skips a plugin, and dir points at a local-plugin directory. It does not install anything.

A plugin must be installed and not disabled to be active.

A plugin imports only from led_ticker.plugin (the curated public surface) and exposes a register(api) function under the led_ticker.plugins entry-point group; api.widget("name")(cls) registers a namespaced widget (<plugin>.<name>). The full surface — the register(api) contract, every registration method, the exported names, and the conventions — is the Plugin API reference. For loader internals, deployment, and known edges, see docs/plugin-system.md.

New to it? The four-page authoring guide walks you from an empty register(api) to an installed widget plugin, step by step. For a complete real-world example, the pool package in the led-ticker-plugins monorepo shows the entry point, tests, and packaging end-to-end.

  • 'pool.monitor' looks like a plugin widget, but no 'pool' plugin is loaded. Install it with led-ticker plugin install pool“ at validate/startup → the plugin isn’t installed. The same hint appears for any namespaced name that doesn’t resolve — a transition, border, color provider, or animation. On Docker, run led-ticker plugin add pool then docker compose restart; from a source checkout, run led-ticker plugin install pool. Either way, led-ticker plugin status confirms what’s registered.
  • A plugin’s widget shows --, dim values, or raises at startup → that’s the plugin’s own runtime/config issue (e.g. pool needs INFLUXDB_TOKEN); check the plugin’s README. A plugin that fails to load is skipped with a logged error and never crashes the display.

See Available plugins for the current list and a link to each plugin’s repository.