Plugins vs Custom Tools

Custom tools (covered in the previous guide) are single Python functions added to one agent. Plugins are structured packages that can contain multiple tools, agent configurations, default settings, dependencies, and documentation — designed to be installed and shared like any Python package.

Custom ToolPlugin
ScopeSingle function, one projectPackage of tools + config, shareable
DistributionCopy Python filepip install or plugin registry
DependenciesManual managementDeclared in setup.cfg / pyproject.toml
ConfigurationHardcoded or env varsDeclarative schema with defaults
VersioningNoneSemantic versioning via package

Plugin Structure

OpenClaw plugins follow a standard directory layout that the plugin registry and installer understand:

my-openclaw-plugin/
├── pyproject.toml              # package metadata and dependencies
├── README.md                   # installation and usage docs
├── my_plugin/
│   ├── __init__.py             # expose plugin entrypoint
│   ├── plugin.json             # plugin manifest (name, version, description)
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── main_tool.py        # tool implementations
│   │   └── helper_tool.py
│   └── agents/
│       └── default_config.yaml # optional bundled agent configs
// plugin.json — plugin manifest
{
  "name": "openclaw-github",
  "version": "1.2.0",
  "description": "GitHub integration tools for OpenClaw agents",
  "openclaw_min_version": "0.8.0",
  "tools": ["list_issues", "create_issue", "get_pr_diff", "post_comment"],
  "config_schema": {
    "github_token": { "type": "string", "env": "GITHUB_TOKEN", "required": true },
    "default_repo": { "type": "string", "required": false }
  }
}

Implementing Plugin Tools

Plugin tools follow the same @tool decorator pattern as custom tools, with the addition of plugin-level configuration access:

from openclaw.tools import tool
from openclaw.plugins import get_plugin_config

@tool
def list_issues(repo: str = None, state: str = "open", limit: int = 20) -> list:
    """
    List GitHub issues for a repository.
    
    Args:
        repo: Repository in owner/name format. Uses plugin default_repo if not specified.
        state: Issue state — 'open', 'closed', or 'all'. Default: 'open'
        limit: Maximum number of issues to return. Default: 20, max: 100
    
    Returns:
        List of issue dicts with keys: number, title, state, labels, url, created_at
    """
    config = get_plugin_config("openclaw-github")
    token = config["github_token"]
    target_repo = repo or config.get("default_repo")
    
    if not target_repo:
        raise ValueError("repo must be specified or plugin default_repo must be configured")
    
    import requests
    headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github.v3+json"}
    response = requests.get(
        f"https://api.github.com/repos/{target_repo}/issues",
        params={"state": state, "per_page": min(limit, 100)},
        headers=headers, timeout=15
    )
    response.raise_for_status()
    issues = response.json()
    return [{"number": i["number"], "title": i["title"], "state": i["state"],
             "labels": [l["name"] for l in i.get("labels", [])],
             "url": i["html_url"], "created_at": i["created_at"]} for i in issues]

Publishing Your Plugin

Plugins can be shared two ways: published to PyPI for open-source distribution, or published to the OpenClaw community plugin registry for discovery via openclaw plugin search.

# Publish to PyPI
pip install build twine
python -m build
twine upload dist/*

# Register with OpenClaw plugin registry (after PyPI publish)
openclaw plugin register --pypi-name openclaw-github --manifest my_plugin/plugin.json
# Users install your plugin with:
openclaw plugin install openclaw-github
# or directly:
pip install openclaw-github

Once installed, plugin tools are immediately available to any agent. Users configure plugin settings in their agent config or environment variables as declared in your plugin.json config schema.

Plugin Development Best Practices

  • Declare all dependencies explicitly in pyproject.toml — pin major versions to avoid surprise breakage
  • Use environment variables for secrets — never hardcode API keys or credentials in tool implementations
  • Write comprehensive docstrings — they directly affect how accurately agents select your tools
  • Include integration tests — test tools against real APIs in a sandbox environment before publishing
  • Follow semantic versioning — breaking changes in tool signatures should increment the major version
  • Provide a bundled agent config — a sensible default agents/default_config.yaml helps users get started quickly

Plugin Configuration Validation

Well-designed plugins validate their configuration at startup rather than failing unpredictably at runtime when a tool is actually called. OpenClaw's plugin system supports an on_load validation hook that runs when the plugin is first loaded:

from openclaw.plugins import plugin_init, get_plugin_config, PluginConfigError

@plugin_init
def validate_config():
    """Called when plugin is loaded — raise PluginConfigError if misconfigured."""
    config = get_plugin_config("openclaw-github")
    token = config.get("github_token")
    if not token:
        raise PluginConfigError("GITHUB_TOKEN must be set (via env var or config)")
    
    # Optionally verify credentials are valid
    import requests
    resp = requests.get("https://api.github.com/user",
                       headers={"Authorization": f"Bearer {token}"}, timeout=5)
    if resp.status_code == 401:
        raise PluginConfigError("GITHUB_TOKEN is invalid or expired")

This pattern surfaces configuration errors immediately (when OpenClaw starts) rather than mid-task, which produces much better error messages and avoids wasted agent work.

Plugin Documentation

Good documentation dramatically increases plugin adoption. At minimum, your plugin README should include: installation command, required configuration (env vars with example values), a quick-start example showing the plugin in action, and the complete list of tools with their purpose and expected inputs/outputs.

Consider including a sample agent-config.yaml that users can copy to get started. A working example that users can adapt is worth more than comprehensive API documentation — most developers start by modifying a working example rather than reading reference docs.

Plugin Lifecycle and Hot Reload

During plugin development, hot reload eliminates the restart-per-change cycle. Start OpenClaw with --dev flag and it watches for changes in registered plugin modules, reloading the tool registry automatically when a .py file changes. This is implemented via Python's importlib.reload and works for tool function body changes; changes to tool signatures (arguments, return types) still require a restart to update the LLM's tool schema. Use hot reload to iterate quickly on tool logic, then restart once to validate the final schema before running against the full agent.

For testing plugins in isolation, the openclaw tool call <tool_name> --args '{"arg": "value"}' command invokes a single tool directly without running the full agent reasoning loop. This is the fastest way to verify tool behavior during development and to build integration test fixtures.