LichtFeld Studio plugins are Python packages discovered from ~/.lichtfeld/plugins/. They can add panels, operators, hooks, tools, capabilities, and plugin-local dependencies.
~/.lichtfeld/
└── plugins/
└── my_plugin/
├── pyproject.toml
├── __init__.py
├── .venv/ # Created by the CLI scaffold or during dependency setup
├── pyrightconfig.json # Created by the CLI scaffold
├── .vscode/ # Created by the CLI scaffold
└── panels/
├── __init__.py
├── main_panel.py
├── main_panel.rml # Scaffolded for v1; optional to customize
└── main_panel.rcss # Scaffolded sibling stylesheet
Discovery is manifest-driven: a plugin is discovered because it has a pyproject.toml with a [tool.lichtfeld] section.
Current implementation lives in:
| Path | Purpose |
|---|---|
src/python/lfs_plugins/ |
Python-side plugin manager, installer, watcher, registry, settings, and templates |
src/python/lfs/py_plugins.cpp |
lichtfeld.plugins bindings |
src/python/lfs/py_ui*.cpp |
Unified panel and UI bindings |
src/python/plugin_runner.cpp |
CLI plugin subcommand runner |
src/core/argument_parser.cpp |
CLI parsing for LichtFeld-Studio plugin ... |
Plugin states:
UNLOADED -> INSTALLING -> LOADING -> ACTIVE
\ \
\ -> ERROR
-> ERROR
Available states:
UNLOADEDINSTALLINGLOADINGACTIVEERRORDISABLED
Inspect them from Python:
import lichtfeld as lf
state = lf.plugins.get_state("my_plugin")
error = lf.plugins.get_error("my_plugin")
traceback = lf.plugins.get_traceback("my_plugin")Two creation paths exist and they are intentionally different:
Creates the v1 source package scaffold:
pyproject.toml
__init__.py
panels/__init__.py
panels/main_panel.py
panels/main_panel.rml
panels/main_panel.rcss
The generated panel still starts as immediate-mode draw(ui) content. The retained files are there so plugin authors can move into hybrid UI without reshaping the package later.
Creates the same source files and then adds:
.venv/.vscode/settings.json.vscode/launch.jsonpyrightconfig.json
This is the convenience path for local development.
v1 intentionally ships a hybrid-ready scaffold. main_panel.py is still the first file you edit, but the sibling main_panel.rml and main_panel.rcss are already wired up with an #im-root mount point for embedded immediate widgets.
The v1 plugin system is strict and does not preserve the old compatibility fields. Every plugin manifest must declare:
[tool.lichtfeld]
hot_reload = true
plugin_api = ">=1,<2"
lichtfeld_version = ">=0.4.2"
required_features = []Rules:
plugin_apitargets the public plugin API contract.lichtfeld_versiontargets the host application/runtime version.required_featuresis a list of optional host features your plugin depends on.- Legacy
min_lichtfeld_version/max_lichtfeld_versionfields are removed in v1 and rejected.
The runtime exposes the current host contract through:
lf.PLUGIN_API_VERSIONlf.plugins.API_VERSIONlf.plugins.FEATURES
lf.ui.Panel is the only public panel base class. It supports three common levels of complexity:
| Level | What you add | Typical files |
|---|---|---|
| Immediate | draw(self, ui) only |
main_panel.py |
| Mixed | style, height_mode, on_update(), on_mount(), on_bind_model() |
main_panel.py |
| Hybrid | template plus optional embedded draw(ui) |
main_panel.py, main_panel.rml, main_panel.rcss |
If a panel uses retained features and leaves template empty, LichtFeld selects a shell automatically:
FLOATING->rmlui/floating_window.rmlSTATUS_BAR->rmlui/status_bar_panel.rml- other retained panel spaces ->
rmlui/docked_panel.rml
Built-in template aliases:
builtin:docked-panelbuiltin:floating-windowbuiltin:status-bar
Use these layers in order:
- Start with
draw(ui)and the immediate widget API. - Add
stylewhen you want retained shell tweaks without introducing a custom template. - Add
template = str(Path(...))and a sibling.rcsswhen you need custom DOM structure or a full stylesheet.
Retained template rules:
- plugin-local
templatevalues should be absolute paths styleis RCSS text, not a filename- a sibling
.rcssfile is loaded automatically for a plugin-local.rml - include
<div id="im-root"></div>in the template when you want embeddeddraw(ui)content
Plugin dependencies live in [project].dependencies inside the plugin's pyproject.toml.
LichtFeld creates per-plugin virtual environments with its bundled Python and uv, using:
uv venv <plugin>/.venv --python <bundled_python> --no-managed-python --no-python-downloadsuv sync --project <plugin> --python <plugin>/.venv/.../python --no-managed-python --no-python-downloads
This keeps plugin packages isolated while ensuring they run against the same Python runtime as the app.
The plugin subcommand currently supports:
LichtFeld-Studio plugin create <name>
LichtFeld-Studio plugin check <name>
LichtFeld-Studio plugin list
The CLI is intentionally narrow. It handles scaffolding, validation, and discovery listing.
The richer management surface is exposed through lichtfeld.plugins:
import lichtfeld as lf
lf.plugins.discover()
lf.plugins.load("my_plugin")
lf.plugins.reload("my_plugin")
lf.plugins.unload("my_plugin")
lf.plugins.load_all()
lf.plugins.start_watcher()
lf.plugins.stop_watcher()
lf.plugins.install("owner/repo")
lf.plugins.update("my_plugin")
lf.plugins.search("viewer")
lf.plugins.install_from_registry("plugin_id")
lf.plugins.check_updates()Quick checks:
LichtFeld-Studio plugin check my_plugin
LichtFeld-Studio plugin listRuntime logging:
import lichtfeld as lf
lf.log.debug("debug")
lf.log.info("info")
lf.log.warn("warn")
lf.log.error("error")- docs/plugins/getting-started.md for the learning path
- docs/plugins/examples/README.md for progressive examples
- docs/plugins/api-reference.md for exact APIs