Plugins extend OpenClaw without modifying its core. They are loaded automatically
when you place their directory in plugins/ and unloaded when you remove it —
no restart required.
| Type | plugin_type value |
Description |
|---|---|---|
| Agent plugin | agent |
Adds one or more new specialist agents |
| Tool plugin | tool |
Adds new tools that all agents can use |
| Provider plugin | provider |
Adds an alternative AI provider |
| Extension plugin | extension |
Combination of agents + tools + config |
plugins/
└── my_plugin/ ← directory name = Python package name
├── __init__.py ← defines the plugin class
├── plugin.yaml ← manifest (REQUIRED)
└── agent.py ← optional: agent implementation
└── tools.py ← optional: tool implementations
# plugins/my_plugin/plugin.yaml
name: my_plugin # Unique identifier (used internally)
version: 1.0.0
description: My example plugin that adds a weather agent
author: Your Name
plugin_type: agent # agent | tool | provider | extension
entry_point: MyPlugin # Class name in __init__.py
# Optional fields
dependencies: [] # pip install requirements
capabilities:
- weather
- forecast
min_openclaw_version: "0.1.0"
homepage: ""
license: MIT# plugins/my_plugin/__init__.py
from openclaw.plugins.plugin_base import BasePlugin, PluginManifest
from openclaw.core.registry import AgentRegistry
from openclaw.core.tool_base import ToolRegistry
from my_plugin.agent import WeatherAgent # relative import won't work here
# Use: from my_plugin import agent as weather_agent_module
class MyPlugin(BasePlugin):
"""Example plugin that adds a WeatherAgent."""
def on_load(
self,
agent_registry: AgentRegistry,
tool_registry: ToolRegistry,
) -> None:
"""Called when the plugin is loaded. Initialise resources here."""
print(f"MyPlugin v{self.manifest.version} loading...")
# Note: don't call agent_registry.register() here.
# Return agents via get_agents() and let PluginManager register them.
def on_unload(self) -> None:
"""Called before the plugin is unloaded. Clean up resources here."""
print("MyPlugin unloading...")
def get_agents(self) -> list:
"""Return agent instances this plugin contributes."""
from my_plugin.agent import WeatherAgent
return [WeatherAgent()]
def get_tools(self) -> list:
"""Return ToolDefinition instances this plugin contributes."""
return [] # This plugin has no standalone tools# plugins/my_plugin/agent.py
from openclaw.agents._agent_mixin import AgentMixin
from openclaw.core.agent_base import BaseAgent
from openclaw.core.session import Session
from openclaw.core.tool_base import ToolDefinition, ToolRegistry
from my_plugin.tools import weather_tools # your custom tools module
class WeatherAgent(AgentMixin, BaseAgent):
name = "weather"
description = "Fetches weather forecasts and climate data."
capabilities = ["weather", "forecast", "climate", "temperature"]
def can_handle(self, task: str) -> float:
keywords = {"weather", "forecast", "temperature", "rain", "sunny", "climate"}
hits = sum(1 for kw in keywords if kw in task.lower())
return min(0.95, 0.2 + hits * 0.2)
def get_tools(self) -> list[ToolDefinition]:
tr = ToolRegistry()
tr.register_module(weather_tools)
return tr.all()
def _build_system_prompt(self) -> str:
return (
"You are a weather specialist. Provide accurate, concise weather "
"information. Always include temperature units and timezone."
)
async def run(self, task: str, session: Session) -> str:
return await self._run(task, session)# plugins/my_plugin/tools.py
import httpx
from openclaw.core.tool_base import tool
@tool(description="Get current weather for a city. Returns temperature, conditions, humidity.")
def get_current_weather(city: str) -> str:
"""Fetch current weather for a city."""
# Example using a public weather API
try:
resp = httpx.get(
f"https://wttr.in/{city}?format=3",
timeout=10
)
return resp.text.strip()
except Exception as exc:
return f"Error fetching weather: {exc}"
@tool(description="Get a 3-day weather forecast for a city.")
def get_weather_forecast(city: str, days: int = 3) -> str:
"""Fetch a multi-day weather forecast."""
try:
resp = httpx.get(
f"https://wttr.in/{city}?format=j1",
timeout=10
)
data = resp.json()
# Parse and format the forecast
forecasts = []
for day in data.get("weather", [])[:days]:
forecasts.append(
f"Date: {day['date']}, Max: {day['maxtempC']}°C, "
f"Min: {day['mintempC']}°C"
)
return "\n".join(forecasts) or "No forecast data available."
except Exception as exc:
return f"Error fetching forecast: {exc}"# The plugin directory already exists in plugins/ — it auto-loads!
# Verify:
openclaw plugins list
# Test:
openclaw run "What is the weather in London?"
openclaw chat --agent weatherIf you add the plugin while OpenClaw is running (and hot_reload is enabled), it will be detected and loaded automatically within ~1 second.
For a plugin that just adds tools (no new agent), use plugin_type: tool:
# plugins/my_tools/__init__.py
from openclaw.plugins.plugin_base import BasePlugin
from my_tools import calculator_tools
class MyToolsPlugin(BasePlugin):
def on_load(self, agent_registry, tool_registry):
# Tools in get_tools() are auto-registered by PluginManager
pass
def on_unload(self):
pass
def get_tools(self):
from openclaw.core.tool_base import ToolRegistry
tr = ToolRegistry()
tr.register_module(calculator_tools)
return tr.all()# plugins/my_tools/plugin.yaml
name: my_tools
version: 1.0.0
description: Calculator tools for mathematical operations
author: Me
plugin_type: tool
entry_point: MyToolsPluginOn startup:
PluginManager.load_all()
└── For each plugins/<dir> with plugin.yaml:
load_plugin(dir)
While running (hot-reload):
New dir appears in plugins/ → load_plugin(new_dir)
Dir removed from plugins/ → unload_plugin(dir_name)
On shutdown:
App.stop() → PluginWatcher.stop()
(loaded plugins remain until process exits)
| Field | Required | Type | Description |
|---|---|---|---|
name |
Yes | string | Unique identifier |
version |
Yes | string | Semantic version (e.g. 1.2.3) |
description |
Yes | string | Short description |
author |
Yes | string | Author name or email |
plugin_type |
Yes | enum | agent, tool, provider, extension |
entry_point |
Yes | string | Class name in __init__.py |
dependencies |
No | list[str] | pip install requirements |
capabilities |
No | list[str] | Routing capability tags |
min_openclaw_version |
No | string | Minimum OpenClaw version |
homepage |
No | string | Plugin homepage URL |
license |
No | string | License identifier |
| Mistake | Fix |
|---|---|
Calling agent_registry.register() inside on_load() |
Return agents from get_agents() instead |
Using relative imports in __init__.py |
Use from my_plugin.submodule import X (absolute package imports) |
entry_point class name doesn't match |
Check spelling; it's case-sensitive |
Missing plugin.yaml |
The watcher won't recognise the directory |
| Tool function raises exceptions | Catch exceptions and return error strings |
| Plugin holds external connections without cleanup | Implement on_unload() to close them |
A plugin is a standard Python package. You can:
- Share as a zip/tarball and unzip into
plugins/ - Publish to PyPI (but then install into
plugins/as a directory) - Include in git as a submodule under
plugins/
The only requirement: it must be a directory (not a .py file) containing plugin.yaml.