Skip to content

Commit d4c565e

Browse files
feat(plugins): auto-generate admin settings UI from settingsSchema (#341) (#1893)
* feat(plugins): auto-generate admin settings UI from settingsSchema Plugins that declare `admin.settingsSchema` in definePlugin() now get an auto-generated settings form in the admin, reachable via a gear icon on the plugin card. Values persist to the plugin's KV store (`settings:` prefix); secrets are write-only and never echoed back. Both GET and PUT require `plugins:manage`. Fixes #341 * fix: resolve settingsSchema for bypassed and runtime-installed plugins Review follow-up: loadBypassedPlugins now forwards settingsSchema to adaptSandboxEntry, and marketplace/registry plugins loaded at runtime resolve their schema from the cached manifest via a new getRuntimePluginSettingsSchema accessor (settings endpoint fallback + hasSettings in the plugin list). * fix(admin): cleared settings fields revert to schema default Review follow-up: emptying a non-secret field now sends null so the server deletes the stored value and the schema default applies again. Previously the empty string was persisted and shadowed the default. * test(plugins): cover runtime-installed plugin settings schema fallback The settings route tests mocked getRuntimePluginSettingsSchema to always return null, so the runtime-installed (marketplace) plugin fallback added to fix the prior review gap was never exercised. Add a GET case where the plugin is absent from configuredPlugins/sandboxedPluginEntries and the runtime lookup supplies the schema, asserting 200 with the schema keys and masked secrets. * fix(admin): correct PluginSettings select items shape and toast usage Addressing emdashbot review: Kumo Select.items expects a value->label Record (or string[]), not {value,label}[] — building the record first so the select trigger renders the chosen label. Also align with admin conventions: use Toast.useToastManager() with type:"error" instead of useKumoToastManager() + variant, and re-export the plugin-settings client functions and SettingField from the api barrel. * style: format * fix(plugins): wrap settings update writes in a transaction The update handler validated every field up front but then ran the delete/set writes in a bare loop, so a failure partway through left the options table half-updated. Wrap the write loop + read-back in withTransaction (real transaction on SQLite/Postgres; degrades to a direct run on D1, which is single-writer so per-statement atomicity still holds), using a transaction-scoped OptionsRepository. --------- Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
1 parent 1cd5c43 commit d4c565e

23 files changed

Lines changed: 1449 additions & 10 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"emdash": minor
3+
"@emdash-cms/admin": minor
4+
---
5+
6+
Adds the auto-generated admin settings form for plugins that declare `admin.settingsSchema`. A gear icon on the plugin's card in Plugins opens a form generated from the schema (string, number, boolean, select, secret, url, and email fields), persisted to the plugin's KV store under `settings:` keys. Secret fields are write-only: the admin shows whether a value is set but never returns it. Editing plugin settings requires the `plugins:manage` permission.

docs/src/content/docs/plugins/creating-native-plugins/your-first-native-plugin.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ admin: {
217217

218218
Field types: `string`, `number`, `boolean`, `select`, `secret`, `url`, `email`. Each accepts `label`, `description`, `default`, plus type-specific extras like `min`/`max`/`options`. Settings are persisted to the same per-plugin KV store sandboxed plugins use — read them with `ctx.kv.get<T>("settings:<key>")` from anywhere.
219219

220+
The generated form appears behind the gear icon on the plugin's card in **Plugins** (admins only — editing plugin settings requires the `plugins:manage` permission). Secret fields are write-only: the admin never sees the stored value, only whether one is set.
221+
220222
For richer settings UI than `settingsSchema` provides, ship custom React pages — see [React admin pages and widgets](/plugins/creating-native-plugins/react-admin/).
221223

222224
## Complete example — audit log plugin

packages/admin/src/components/PluginManager.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,25 @@ function PluginCard({
430430
</RouterLinkButton>
431431
)}
432432

433+
{plugin.hasSettings && plugin.enabled && (
434+
<RouterLinkButton
435+
to="/plugins-manager/$pluginId/settings"
436+
params={{ pluginId: plugin.id }}
437+
aria-label={t`Settings`}
438+
variant="ghost"
439+
shape="square"
440+
icon={<Gear />}
441+
/>
442+
)}
443+
433444
{plugin.hasAdminPages && plugin.enabled && (
434445
<RouterLinkButton
435446
to="/plugins/$pluginId/$"
436447
params={{ pluginId: plugin.id, _splat: "" }}
437-
aria-label={t`Settings`}
448+
aria-label={t`Plugin pages`}
438449
variant="ghost"
439450
shape="square"
440-
icon={<Gear />}
451+
icon={<FileText />}
441452
/>
442453
)}
443454

0 commit comments

Comments
 (0)