Skip to main content

Plugins

A manifest, in full

PluginManifest(
name="checkout", # the identifier other plugins depend on
version="1.0.0",
display_name="Checkout", # what a person is shown
description="Prices a cart and turns it into an order.",
octicon="credit-card",
emoji="💳",
dependencies=["catalog"], # backend: enforced at registration
frontend_dependencies=["@app/checkout"], # declared, not enforced
optional_frontend_dependencies=["@app/header"],
)

manifest.title returns display_name or, failing that, name — so a host always has something to print.

The four presentation fields are the same four the TypeScript tier declares. That is what lets one plugin list, one overlay and one graph draw a plugin from either side of the wire.

Registering one

from reactor import PluginManifest, PluginPlatform
from reactor.hooks import hookimpl

class CatalogPlugin:
@hookimpl
def on_reactor_start(self, tenant_id: str | None = None) -> None:
...

def provide_routes(self) -> list[dict]:
return [{"path": "/api/catalog/songs", "method": "GET", "plugin": "catalog"}]

platform = PluginPlatform()
platform.register_plugin(CATALOG_MANIFEST, CatalogPlugin())
platform.start()

dependencies are enforced: the platform refuses a plugin whose declared dependencies are not registered yet, which is why a host registers in dependency order. See the music backend for a host doing exactly that with four packages.

Compatibility

PluginManifest(
name="catalog",
version="1.0.0",
compatibility=PluginCompatibility(api_version="v1"),
)

Discovery

platform.discover(group) registers whatever is advertised under an entry-point group, so installing a distribution publishes its plugins and nothing is hardcoded in the host. Combined with a factory, discovery is lazy in the same spirit as a lazy plugin in the browser: installed, listed, and not constructed until wanted.

Packaging a frontend extension the same way — one pip install delivering both tiers — is described under Python-packaged extensions.