This guide is a compact, generation-friendly reference for building correct AstrBot plugins.
your_plugin/
├── main.py # Plugin entry (required)
├── metadata.yaml # Plugin metadata (required by plugin packaging flow)
├── requirements.txt # Python deps (recommended/usually required)
└── _conf_schema.json # Optional: config schema for AstrBot UI/config system
- Do not write mutable data into plugin source directory.
- Use:
- KV storage APIs (
context.provider) - plugin data directory (e.g.
data/plugin_data/<plugin_name>/) for larger files.
- KV storage APIs (
from astrbot.api.event import filter, AstrMessageEvent
from astrbot.api.star import Context, Star, register
@register("hello_plugin", "your_name", "Minimal AstrBot plugin", "0.1.0")
class HelloPlugin(Star):
def __init__(self, context: Context):
super().__init__(context)
@filter.command("hello")
async def hello(self, event: AstrMessageEvent):
yield event.plain_result("hello")from astrbot.api.star import Context, Star, register
from astrbot.api.event import filter, AstrMessageEventStar: plugin base class. Your plugin class must inherit this.@register(name, author, desc, version): registers plugin metadata.AstrMessageEvent: incoming message/event object.event.plain_result(text): plain text response payload.- Handler outputs usually use
yield ...in command handlers.
context.provideris used for storage and framework capabilities.- KV storage methods (common):
put_kv_data(namespace, key, value)get_kv_data(namespace, key)delete_kv_data(namespace, key)
@filter.command("ping")
async def ping(self, event: AstrMessageEvent):
yield event.plain_result("pong")@filter.command("echo")
async def echo(self, event: AstrMessageEvent, text: str):
yield event.plain_result(text)@filter.command("weather", alias={"w", "天气"})
async def weather(self, event: AstrMessageEvent, city: str):
yield event.plain_result(f"querying: {city}")dev = filter.command_group("dev")
@dev.command("status")
async def dev_status(self, event: AstrMessageEvent):
yield event.plain_result("ok")- Use
@filter.*decorators to match message/event conditions. - Multiple filters are combined with logical AND.
@filter.event_message_type(...)
@filter.permission_type(...)
async def on_message(self, event: AstrMessageEvent):
yield event.plain_result("matched")- In normal command handlers: usually
yield event.plain_result(...). - In some async callback contexts (e.g. session waiter callback): use
await event.send(...).
Defines plugin config schema shown in AstrBot management/UI and persisted as config.
Typical schema fields include:
type(e.g.string,int,bool, etc.)descriptiondefault- enum/options related fields
- special selectors (
_special) when needed by framework features.
from astrbot.api import AstrBotConfig
from astrbot.api.star import Context, Star, register
@register("cfg_demo", "you", "config demo", "0.1.0")
class CfgDemo(Star):
def __init__(self, context: Context, config: AstrBotConfig):
super().__init__(context)
self.config = config
self.api_key = config.get("api_key", "")
async def save_something(self):
self.config["last_status"] = "ok"
self.config.save_config()- Plugin class must inherit
Star. - Plugin class must be decorated by
@register(...). - Command handlers must use
@filter.command(...)(or command-group subcommands). main.pymust be valid Python and importable.
- Keep plugin async-friendly.
- Add dependencies to
requirements.txt. - Include error handling for external API/network/file operations.
- Keep code formatted/linted (e.g.
ruff format .andruff check .). - Use clear comments only where necessary.
- Prefer async HTTP clients (
httpx,aiohttp) over blockingrequests. - Validate user input and command parameters.
- Isolate side effects (file writes, external commands) behind helper functions.
- Use path-safe operations (
pathlib.Path, resolved path checks) if touching files. - Keep handlers small; move logic to services/modules.
- Return clear user-facing error messages on failure.
- For multi-turn workflows, use session control instead of ad-hoc global state.
from astrbot.api.event import filter, AstrMessageEvent
from astrbot.api.event.filter import session_waiter
from astrbot.core.star.filter.event_message_type import EventMessageType
from astrbot.core.star.filter.permission_type import PermissionType
from astrbot.core.star.session import SessionController
@filter.command("survey")
async def survey(self, event: AstrMessageEvent):
yield event.plain_result("Your age?")
@session_waiter(timeout=60)
async def wait_age(controller: SessionController, event: AstrMessageEvent):
if not event.message_str.isdigit():
await event.send(event.plain_result("Please input a number"))
controller.keep(timeout=60)
return
await event.send(event.plain_result(f"Age={event.message_str}"))
controller.stop()Key rule:
- In waiter callback, use
await event.send(...)and control lifecycle withcontroller.keep()/controller.stop().
- Missing
@register(...)or wrong class not inheritingStar. - Defining handlers without
@filter.command(...). - Using blocking calls (
requests, long sync I/O) in async handlers. - Writing runtime data into plugin code directory.
- Not declaring third-party deps in
requirements.txt. - Incorrect reply style in callback contexts (
yieldwhereawait event.sendis required). - Omitting input validation and exception handling.
- Hardcoding secrets/API keys in source code.
- Creating overly complex state machines when session control already solves multi-turn chat.
Use this checklist before outputting a plugin:
main.pyexists and is importable.- Correct imports from
astrbot.api.starandastrbot.api.event. - Class inherits
Star. - Class decorated with
@register. - At least one
@filter.commandhandler. - Handler returns response via
yield event.plain_result(...). - Async-safe external calls and robust
try/except. requirements.txtincludes all non-stdlib packages.- Optional
_conf_schema.json+AstrBotConfigusage if config needed. - No unsafe path handling or unmanaged runtime writes.