config_schema declarative form
config_schemais enough for simple configuration — the host renders it automatically into the workshop card's inline form / a standalone settings tab. Ship your ownsettings_viewQML only when you need custom UX such as "Get API Key" or "Test Connection" (see Custom settings view).
Basic form
"config_schema": [
{ "key": "api_key", "label": "API Key", "type": "password", "is_secret": true },
{ "key": "base_url", "label": "API 地址", "type": "string",
"default": "https://api.example.com" },
{ "key": "temperature", "label": "温度", "type": "number", "default": 0.3 },
{ "key": "model_name", "label": "模型", "type": "select",
"choices": ["a", "b"], "default": "a" },
{ "key": "enable_stream", "label": "流式", "type": "boolean", "default": true },
{ "key": "port", "label": "服务端口", "type": "number", "default": 18080,
"description": "本地服务监听端口,留空用默认;被占用时自动顺延" }
]Every item must have a key and a label.
Fields
| Field | Type | Description |
|---|---|---|
key | string | Required. self._config.get(key) reads the value with it |
label | string | Required. Display name |
type | string | See the table below; defaults to string |
default | any | Default value |
description | string | Helper text shown under the field |
choices | string[] | Options for select (an object form {"value","label"} is also accepted) |
choice_labels | object | Stored value → display name map, e.g. {"x2_john": "John · 英语男声"} |
is_secret | bool | true → masked input (same as type: "password") |
presets | object | Selecting one option back-fills other fields (see below) |
preset_options | array | Gives a text input one-click preset chips |
hidden | bool | Not rendered, but the key stays valid |
show_when | object | Conditional visibility (see below) |
Available type values
| Value | Rendering |
|---|---|
string | Single-line text (renders as a dropdown automatically when choices is present) |
number | Numeric input (short width) |
boolean | Switch |
select | Dropdown (with choices) |
password | Masked input |
textarea / text | Multi-line text |
file | ⚠️ The manifest type keeps this value, but the dynamic form has no file-picker widget yet (it renders as a text box). To pick files, ship your own settings_view or use host.pick_file() |
Preset back-fill presets
Switching provider changes base_url / model automatically, so the user only has to paste the key:
{
"key": "provider_preset",
"label": "服务商预设",
"type": "select",
"choices": ["小米 Token Plan", "DeepSeek", "Ollama 本地", "自定义"],
"default": "小米 Token Plan",
"presets": {
"小米 Token Plan": { "base_url": "https://…", "model": "mimo-v2.5" },
"Ollama 本地": { "base_url": "http://127.0.0.1:11434/v1", "model": "qwen2.5" }
}
}preset_options gives a text input one-click choices (URL / port):
{ "key": "base_url", "label": "API 地址", "type": "string",
"preset_options": [
{ "label": "官方", "value": "https://api.example.com" },
{ "label": "本地", "value": "http://127.0.0.1:8000" }
] }Declarative visibility: hidden + show_when
Show only the parameters that actually apply to the current model / variant, and collapse the fields that belong to other models. Principles:
"hidden": true: not rendered, but the key stays valid (the host or your code can still write it). Typical case: voices are managed centrally by the host's "Voice" dropdown, so the plugin does not need to expose avoiceinput (the host writes the selected value intoself._config["voice"])."show_when": {...}: rendered only when the current config matches.
{ "key": "variant", "label": "模型变体", "type": "select",
"choices": ["base", "customvoice", "voicedesign"], "default": "base" },
{ "key": "instruct", "label": "音色描述", "type": "string",
"show_when": { "variant": "voicedesign" },
"description": "用文字描述想要的音色(仅 voicedesign 变体生效)" }The accepted shapes of show_when:
| Form | Meaning |
|---|---|
{"variant": "voicedesign"} | Shown only when it equals that value |
{"variant": ["a", "b"]} | Shown when it matches any one of them |
{"variant": "*"} | Shown when that key is non-empty |
It only looks at the current config values; when a condition field's value changes, the UI rebuilds the corresponding row automatically (no lost focus, no flicker). The host needs no code change — adapting to a new model only means editing your mod.json.
If a field belongs to another model, write another plugin /
mod.jsoninstead of piling everything into one form.
Compute device (device)
Local inference models (llama.cpp / ONNX / PyTorch…) should declare device in config_schema:
{
"key": "device",
"label": "计算设备",
"type": "select",
"choices": ["vulkan", "rocm", "cuda", "cpu"],
"default": "vulkan",
"description": "推理硬件后端:Vulkan(跨平台 GPU)、ROCm(AMD 显卡)、CUDA(NVIDIA 显卡)或 CPU。"
}The standard device identifiers the host supports natively are rendered automatically as plain-language labels — you do not write a display name per option:
| Value | Host display name |
|---|---|
cuda | GPU (CUDA) |
vulkan | GPU (Vulkan) |
rocm | GPU (AMD ROCm) |
cpu | CPU |
directml | GPU (DirectML) |
openvino | OpenVINO |
Only declare backends your runtime really supports — writing cuda for a llama.cpp runtime that only has Vulkan+CPU means users pick it and crash. (One workable approach: generate choices dynamically from your runtime's probe results.)
Other behaviour:
- Smart hardware gating: when the config or the defaults contain
vulkan/rocm, the host automatically allows non-NVIDIA hardware; on an AMD GPU it rates the setup "Good fit" instead of popping the "NVIDIA CUDA required" red box. - Local models (declaring
model_artifacts+tags: ["local"]) get their device selection from the settings page'sLocalLoadDeviceRow; your form'sdeviceis then hidden automatically (to avoid two duplicate controls) — so just declare it as usual and don't worry about visibility.
Consuming config
| Scenario | How |
|---|---|
| Python plugin | self._config.get(key); update_config(delta) is called on change |
| QML component | Read with bridge.getPluginConfig(pluginId) and listen to bridge.pluginConfigChanged via Connections (mandatory, otherwise there is no hot refresh, see UI extensions) |
When settings_view is mandatory
The following UX is not generated from config_schema and needs your own ui_extensions.settings_view:
- A "Get API Key" button / application link
- A "Test Connection" button and its result display
- Mode switches such as dual Base URL or Token Plan / normal API
- Any UI that has to call the host's
host.pick_file()or show a dynamic list
See Custom settings view.
Related
- Custom settings view
- Layout & mod.json — full field table
- Config & private data
- Local-service engine contract —
port/deviceconventions
