← Back to the blog
Plugins #marketplace

The plugin marketplace: extend without forking

Nine built-in plugins and a framework that discovers them from the filesystem — no central registry to edit. How StreamHub lets features plug into both the backend and the dashboard.

2 min read
Leer en español

Features that plug in, not fork in

StreamHub ships a small plugin framework so features can attach to both the backend and the dashboard with zero edits to any central registry. A plugin is just a file dropped into a known folder; the framework discovers it, lists it in the per-app Plugins marketplace, validates its config against a typed schema, and — when the plugin declares one — owns the lifecycle of its worker process.

Install is per app, and every plugin is enabled, configured and uninstalled independently.

The nine built-in plugins

Plugin What it does
Cockpit Drag-and-drop CCTV grid of every live stream in the app.
Quality Bandwidth + latency test as a green/amber/red traffic light.
Radio Audio-only WebRTC radio: go on air, count listeners.
Video Streaming Go live from webcam + mic, forward the room to RTMP.
Timestamp A live CCTV-style date/time stamp on the player.
Watermark A text watermark in a corner of the player.
Scheduled Live A countdown cover that hides the stream until a premiere time.
YOLO A Python worker runs object detection and posts boxes to the overlay.
Deface A Python worker detects faces; the player blurs them client-side.

How discovery works

  • Backend — drop plugin.meta.ts default-exporting definePlugin({...}). A registry service globs the filesystem and imports each manifest. That manifest is the source of truth for the plugin's id, category (tool | processor | panel), UI slot (app-tab | panel | player-overlay), and its configSchema — where every field has a default, so a fresh install is valid immediately.
  • Frontend — drop index.tsx default-exporting a PluginModule; discovery picks it up via import.meta.glob.

Workers, owned for you

A plugin that sets needsWorker: true provides a pure worker.spawn(ctx) returning {command, args, env}. The framework then owns the whole process lifecycle — enabling starts it, disabling stops it, and start/stop/status/logs are exposed over the API. YOLO and Deface both spawn a Python process this way; the core never needs to know what the worker actually is.

Installing a plugin

curl -X POST $BASE/apps/live/plugins/cockpit/install \
  -H "Authorization: Bearer $TOKEN"

curl -X PATCH $BASE/apps/live/plugins/cockpit \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"enabled": true}'

That is the whole extensibility story: features are files, discovery is automatic, and the marketplace is per app.