Plugins can declare settings that render automatically in the web UI — no JavaScript needed. For complex interactive UIs, a custom web module is also supported.
Declare settings in plugin.json and they auto-render in Settings > Plugins:
"capabilities": {
"settings": [
{"key": "api_key", "type": "string", "label": "API Key", "default": "", "widget": "password", "help": "Your API key"},
{"key": "units", "type": "string", "label": "Units", "default": "metric", "options": [{"label": "Metric", "value": "metric"}, {"label": "Imperial", "value": "imperial"}]},
{"key": "cache_min", "type": "number", "label": "Cache (min)", "default": 15},
{"key": "enabled", "type": "boolean", "label": "Enabled", "default": true}
]
}| Field | Required | Description |
|---|---|---|
key |
yes | Setting key (unique within plugin) |
type |
yes | "string", "number", "boolean" |
label |
yes | Display name |
default |
yes | Default value |
help |
no | Description text |
widget |
no | Override: "textarea", "password", "select", "radio" |
options |
no | [{label, value}] for select/radio |
placeholder |
no | Input hint text |
confirm |
no | Danger confirm gate (see below) |
Widget inference when omitted: string -> text, string + options -> select, number -> number spinner, boolean -> toggle.
Any field can have a confirm object that shows a danger dialog when a specific value is selected:
{
"key": "validation", "type": "string", "label": "Validation", "default": "moderate",
"confirm": {
"values": ["trust"],
"title": "Trust Mode",
"warnings": ["Warning 1", "Warning 2"],
"buttonLabel": "Enable Trust Mode"
}
}Settings are stored at user/webui/plugins/{name}.json and read via plugin_loader.get_plugin_settings(name) (merges stored values with manifest defaults).
For settings that need custom JavaScript beyond what manifest settings provide, plugins can ship a web/ subdirectory.
Most plugins should use manifest settings instead — it's simpler and requires no JavaScript. Use web only for complex interactive UIs.
"capabilities": {
"web": {
"settingsUI": "plugin"
}
}plugins/my-plugin/
plugin.json
web/
index.js # Entry point (required)
style.css # Optional
Assets served at /plugin-web/my-plugin/index.js.
import { registerPluginSettings } from '/static/shared/plugin-registry.js';
import pluginsAPI from '/static/shared/plugins-api.js';
export default {
name: 'my-plugin',
init(container) {
registerPluginSettings({
id: 'my-plugin',
name: 'My Plugin',
icon: '⚙️',
helpText: 'Configure my plugin',
render(container, settings) {
container.innerHTML = `
<input type="text" id="mp-url" value="${settings.url || ''}">
`;
},
load: () => pluginsAPI.getSettings('my-plugin'),
save: (settings) => pluginsAPI.saveSettings('my-plugin', settings),
getSettings(container) {
return { url: container.querySelector('#mp-url').value };
}
});
},
destroy() { }
};| Method | Endpoint | Description |
|---|---|---|
| GET | /api/webui/plugins/{name}/settings |
Read settings |
| PUT | /api/webui/plugins/{name}/settings |
Save settings |
| DELETE | /api/webui/plugins/{name}/settings |
Reset to defaults |