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.
Where plugins stand today
/widgets/:filename, rendered in same-origin iframes (PluginWidgetWrapper, sandbox allows scripts + same-origin).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
pluginSettingsblob 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:
POST /api/users/:id/clams/reduce).If the platform can express this plugin cleanly, it's right.