Skip to content

feat: Plugin extensibility #105

Description

@jherforth

Scope note: this issue is the plugin platform — letting plugins go beyond static HTML and interact with core functionality. The database-flexibility half of the original discussion (external Postgres/MariaDB, ORM adoption) was split out to #119. Refined 2026-07-05 after a code review of the current plugin system.

Where plugins stand today

  • Plugins are single HTML files served at /widgets/:filename, rendered in same-origin iframes (PluginWidgetWrapper, sandbox allows scripts + same-origin).
  • Because they're same-origin, plugins can already call the entire REST API — the custom widget guide documents this, including the CORS proxy for external services. There is no auth, so plugins are fully trusted.
  • What plugins cannot do: react to things happening in core (no events), persist their own data (no storage), or expose their own settings in the Admin Panel (only enabled/transparent/refresh interval exist).

So "advanced plugins" isn't about granting API access — it's about adding the missing platform pieces and formalizing the implicit ones.

The ask: four platform capabilities

(a) A versioned plugin API contract. Formalize which endpoints plugins may rely on (e.g. /api/plugin/v1/... or a documented stable subset), so core refactors don't silently break community plugins. This also opens the door to a capability/permission model later — today every plugin is fully trusted, which is fine for self-hosted but worth designing an off-ramp for.

(b) An event system. Core emits domain events — e.g. clam.withdrawn, clam.deposited, chore.completed — and plugins can subscribe. Design questions: server-side webhooks/handlers vs. client-side delivery into the iframe (postMessage bridge or SSE), and whether plugin handlers can react only or also participate (e.g. add a siphon amount to a withdrawal).

(c) Plugin-owned storage. A namespaced persistence surface (e.g. /api/plugin/v1/storage/:pluginId/... key-value or documents) so a plugin can keep state like bucket balances server-side, surviving devices and reloads.

(d) Per-plugin settings. Plugins declare settings (name/type/default — e.g. a numeric "siphon" value), the Admin Panel renders them in the existing Plugins tab, and values are delivered to the plugin. The current per-device pluginSettings blob is a starting point, though platform settings like these are probably household-wide rather than per-device.

A plugin manifest (declared events, settings schema, storage use, API version) likely ties (a)–(d) together and replaces the current name+filename registry entry.

Reference use case: clam buckets (spend / save / give)

The motivating example, mapped to the capabilities:

  • In our house 15 clams = $10. Kids can withdraw clams (mechanism mostly exists: POST /api/users/:id/clams/reduce).
  • On each withdrawal, the plugin siphons an extra configurable amount (e.g. 2 clams) into a shared give bucket → needs (b) an event on withdrawal (ideally participating, to take the siphon atomically) and (d) a siphon-value setting.
  • Bucket balances (spend/save/give per kid + the family give pool) live in the plugin's own bank → needs (c) storage.
  • When the give bucket crosses a threshold, the family discusses what to donate to → plugin UI over its own data, calling core APIs per (a).

If the platform can express this plugin cleanly, it's right.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Status
Backlog

Relationships

None yet

Development

No branches or pull requests

Issue actions