Configuration Management
MaiBot plugins support a declarative configuration management mechanism. By defining strongly-typed configuration models through PluginConfigBase and Field, the Runner automatically generates default configurations, fills in missing fields, and exposes renderable configuration schemas to the WebUI.
Configuration File Location
Each plugin's configuration file is located under the plugin directory at config.toml:
my_plugin/
├── plugin.py # Plugin entry point
├── config.toml # Plugin configuration (optional)
└── _manifest.json # Plugin metadataconfig.toml vs _manifest.json
config.toml: The plugin's runtime configuration (feature toggles, parameters, etc.), read by the plugin itself_manifest.json: The plugin's metadata (ID, version, dependencies, etc.), validated and managed by the Host
The two serve completely different purposes and should not be confused.
PluginConfigBase Configuration Model
Basic Usage
from maibot_sdk import MaiBotPlugin, PluginConfigBase, Field
class MyPluginConfig(PluginConfigBase):
"""Plugin complete configuration"""
__ui_label__ = "Plugin Configuration"
enabled: bool = Field(default=True, description="Whether to enable the plugin")
greeting: str = Field(default="Hello!", description="Default greeting")
max_retries: int = Field(default=3, description="Maximum number of retries")
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_load(self) -> None:
# Access strongly-typed configuration via self.config
self.ctx.logger.info("Current greeting: %s", self.config.greeting)
self.ctx.logger.info("Max retries: %d", self.config.max_retries)Nested Configuration
Implement grouped configuration by nesting PluginConfigBase classes:
from maibot_sdk import MaiBotPlugin, PluginConfigBase, Field
class PluginSection(PluginConfigBase):
"""Plugin basic configuration"""
__ui_label__ = "Basic Settings"
enabled: bool = Field(default=True, description="Whether to enable the plugin")
greeting: str = Field(default="Hello!", description="Default greeting")
class AdvancedSection(PluginConfigBase):
"""Advanced configuration"""
__ui_label__ = "Advanced Settings"
max_retries: int = Field(default=3, description="Maximum number of retries")
timeout: float = Field(default=30.0, description="Timeout duration (seconds)")
class MyPluginConfig(PluginConfigBase):
"""Plugin complete configuration"""
plugin: PluginSection = Field(default_factory=PluginSection)
advanced: AdvancedSection = Field(default_factory=AdvancedSection)
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_load(self) -> None:
# Access nested configuration
self.ctx.logger.info("Greeting: %s", self.config.plugin.greeting)
self.ctx.logger.info("Timeout: %s", self.config.advanced.timeout)Field
Field is used to declare metadata for configuration fields:
from maibot_sdk import Field
Field(
default=..., # Default value
default_factory=..., # Default value factory function (for mutable default values)
description="...", # Field description (displayed in WebUI)
)defaultAny— Field default valuedefault_factoryCallable— Default value factory function, used for mutable types likelist,dict, nestedPluginConfigBase, etc.descriptionstr— Field description, displayed as a form label in the WebUIjson_schema_extradict— Additional metadata passed to the WebUI Schema generator. Common keys:placeholder(input box placeholder text),group(UI grouping hint)
ui_label
PluginConfigBase subclasses can set the group title displayed in the WebUI via the __ui_label__ class attribute:
class PluginSection(PluginConfigBase):
__ui_label__ = "Basic Settings" # Title displayed in WebUI
enabled: bool = Field(default=True, description="Whether to enable the plugin")ui_icon
PluginConfigBase subclasses can set the group icon displayed in the WebUI via the __ui_icon__ class attribute, accepting Material Icons icon names:
class PluginSection(PluginConfigBase):
__ui_label__ = "Basic Settings"
__ui_icon__ = "settings" # Material Icons icon name displayed in WebUI
enabled: bool = Field(default=True, description="Whether to enable the plugin")ui_order
PluginConfigBase subclasses can set the display order of groups in the WebUI via the __ui_order__ class attribute. Lower values appear first:
class PluginSection(PluginConfigBase):
__ui_label__ = "Basic Settings"
__ui_icon__ = "settings"
__ui_order__ = 0 # Sorting weight for the group in WebUI; lower numbers appear first
enabled: bool = Field(default=True, description="Whether to enable the plugin")json_schema_extra
json_schema_extra is used to pass additional metadata to the WebUI Schema generator. Common use cases include:
placeholder: Placeholder hint text for the input boxgroup: Configuration grouping hint in the WebUI
class MyPluginConfig(PluginConfigBase):
"""Plugin complete configuration"""
greeting: str = Field(
default="Hello!",
description="Default greeting",
json_schema_extra={"placeholder": "Please enter a greeting", "group": "basic"}
)
api_key: str = Field(
default="",
description="API Key",
json_schema_extra={"placeholder": "Please enter API Key", "group": "advanced"}
)Accessing Configuration
Strongly-Typed Access (self.config)
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_load(self) -> None:
# Strongly-typed access, with code completion and type checking
greeting = self.config.plugin.greeting
timeout = self.config.advanced.timeout注意
- Calling
self.configwithout declaringconfig_modelwill raiseRuntimeError - Calling
self.configbefore the configuration is injected will also raiseRuntimeError
Raw Dictionary Access
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_load(self) -> None:
# Get raw configuration dictionary
raw = self.get_plugin_config_data()
greeting = raw.get("plugin", {}).get("greeting", "Default value")get_plugin_config_data() is always available, returns dict[str, Any], and does not require declaring config_model.
Configuration Hot Reload
When the config.toml file changes, the Runner automatically triggers the on_config_update() callback:
from maibot_sdk import MaiBotPlugin, CONFIG_RELOAD_SCOPE_SELF
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
if scope == CONFIG_RELOAD_SCOPE_SELF:
# self.config is automatically updated to the latest value
self.ctx.logger.info("Configuration updated, new greeting: %s", self.config.plugin.greeting)::: important self.config is automatically updated when on_config_update(scope="self") is called, so there is no need to manually re-read it. :::
For more on configuration hot reloading, see Lifecycle.
config.toml Format
The configuration file uses the TOML format, corresponding to the nested structure of PluginConfigBase:
[plugin]
config_version = "1.0.0"
enabled = true
greeting = "Hello!"
[advanced]
max_retries = 3
timeout = 30.0config_version
config_version is a special field used to track the configuration version. The Runner preserves this field when merging default configurations.
Default Configuration and Schema Generation
Auto-Fill
When certain fields are missing in config.toml, the Runner automatically fills them based on the default values of config_model:
# If config.toml only has:
# [plugin]
# enabled = false
# The Runner will automatically fill in the default values for the greeting and advanced sectionsWebUI Schema
After declaring config_model, the Runner automatically generates a WebUI-renderable configuration Schema:
# Method on the plugin class (usually does not need to be called manually)
schema = MyPlugin.build_config_schema(
plugin_id="com.example.my-plugin",
plugin_name="My Plugin",
plugin_version="1.0.0",
)The WebUI renders a configuration form based on the Schema, allowing users to edit the configuration directly in the browser.
Reading Configuration via API
In addition to using self.config and self.get_plugin_config_data(), you can also read configuration through the capability proxy:
# Read the plugin's own configuration
value = await self.ctx.config.get("plugin.greeting")
# Read other plugin's configuration
value = await self.ctx.config.get_plugin("com.other.plugin")
# Read global Bot configuration
all_config = await self.ctx.config.get_all()Not Using config_model
If the plugin configuration is very simple, you can omit declaring config_model and directly use ctx.config and get_plugin_config_data():
class SimplePlugin(MaiBotPlugin):
# Do not declare config_model
async def on_load(self) -> None:
# Can only read via raw dictionary or ctx.config
raw = self.get_plugin_config_data()
name = raw.get("name", "Default Name")
# self.config will raise a RuntimeError
# Do not call self.configHowever, it is recommended to always use config_model for better type safety and WebUI integration.