Skip to content

Plugin Overview

TeamWeb AI’s plugin system supports five types of plugins. Each type extends the platform in a different way:

TypePurposeImplementation fileGuide
ToolAdds tools that assistants can use during conversationstools.pyTool Plugins
ChannelAdds communication channels (email, messaging, voice)channel.pyChannel Plugins
LLMAdds a language model providerprovider.pyProvider Plugins
EmbeddingAdds an embedding model providerprovider.pyProvider Plugins
OutputAdds structured output type definitions for tasksoutputs.pyOutput Plugins

Plugin Structure

Every plugin lives in its own directory and must contain a manifest.py file that describes the plugin. The directory name is used as the plugin identifier during loading.

my_plugin/
    __init__.py        # Optional
    manifest.py        # Required — plugin metadata
    tools.py           # Required for tool plugins
    channel.py         # Required for channel plugins
    provider.py        # Required for LLM and embedding plugins
    outputs.py         # Required for output plugins

Installing User Plugins

TeamWeb AI discovers plugins from two locations at startup:

  1. System plugins — shipped with TeamWeb AI in app/plugins/
  2. User plugins — loaded from the directory set by the PLUGIN_DIR environment variable (defaults to /data/plugins)

To install a custom plugin, place its directory inside the PLUGIN_DIR path:

/data/plugins/
    my_tool_plugin/
        manifest.py
        tools.py
    my_channel_plugin/
        manifest.py
        channel.py

Restart the application after adding or modifying plugins. TeamWeb AI logs each plugin it discovers at startup.

Technical Details

Discovery process — At application startup, the plugin registry scans both directories for subdirectories containing a manifest.py file. Each manifest is imported, validated, and registered. The registry enforces globally unique plugin names and tool names — duplicate names raise an error at startup to prevent conflicts.

Graceful degradation — If a plugin fails to load (due to an import error, missing dependency, or invalid manifest), the error is logged and other plugins continue loading normally. One broken plugin does not prevent the application from starting. Check the application logs if your plugin doesn’t appear in the UI.

Configuration storage layers — Plugin configuration is stored at different levels reflecting how each plugin type is used. Tool plugin configuration is stored per project (different projects may need different API keys). Channel plugin configuration is stored per assistant (each assistant has its own bot token or webhook settings). LLM and embedding provider configuration is stored globally (one active provider for the entire installation). Tool plugins can additionally define per-assistant tool configuration for settings that vary between assistants using the same tool.

Directories that start with an underscore (_) are ignored during plugin discovery.

The Manifest

Every plugin requires a manifest.py file that exports a PluginManifest instance named manifest:

from app.plugins.base import PluginManifest, PluginType

manifest = PluginManifest(
    name="my_plugin",
    plugin_type=PluginType.TOOL,
    version="1.0.0",
    description="A short description shown in the UI",
    author="Your Name",
    config_schema={
        "api_key": {
            "type": "string",
            "required": True,
            "secret": True,
            "label": "API Key",
            "placeholder": "sk-...",
            "help_text": "Your service API key",
        },
        "base_url": {
            "type": "string",
            "required": False,
            "label": "Base URL",
            "default": "https://api.example.com",
        },
    },
    sandboxable=False,
    setup_docs="<p>Static HTML setup instructions.</p>",
)

Manifest Fields

FieldTypeDescription
namestrUnique identifier for the plugin
plugin_typePluginTypeOne of TOOL, CHANNEL, EMBEDDING, LLM, OUTPUT
versionstrSemantic version string
descriptionstrHuman-readable description shown in the UI
authorstrPlugin author name
config_schemadictConfiguration fields (see below)
sandboxableboolWhether tool execution can run in a Docker sandbox (tool plugins only)
setup_docsstrStatic HTML with setup instructions shown in the configuration UI
channel_typestrFor channel plugins: "email", "messaging", or "voice". Only one channel of each type may be active per assistant

Config Schema Fields

Each key in config_schema defines a configuration field that appears in the plugin settings form:

PropertyTypeDescription
typestrJSON Schema type ("string", "integer", "boolean")
requiredboolWhether the field must be filled in
secretboolMask the value in the UI after saving (for API keys, tokens, etc.)
labelstrDisplay label for the form field
placeholderstrPlaceholder text
defaultanyDefault value
help_textstrHelp text shown below the field

Dynamic Setup Docs

Instead of (or in addition to) static setup_docs, you can define a get_setup_docs function in your manifest module to generate setup instructions dynamically. This is useful for including environment-specific values like webhook URLs:

from typing import Any

def get_setup_docs(app_config: dict[str, Any], **kwargs: Any) -> str:
    """Return setup instructions with the actual webhook URL."""
    public_url = app_config.get("PUBLIC_URL", "https://yourdomain.com")
    webhook_url = f"{public_url}/webhooks/channels/my_channel/inbound"
    return (
        "<h6>Setup</h6>"
        "<ol>"
        f"<li>Set your webhook URL to: <code>{webhook_url}</code></li>"
        "</ol>"
    )

Configuration Layers

Plugin configuration is stored at different levels depending on the plugin type:

Plugin typeConfiguration levelWhere it’s stored
ToolPer projectProjectPlugin.config_json
ChannelPer assistantAssistantPlugin.config_json
LLMGlobalCorePluginConfig.config_json
EmbeddingGlobalCorePluginConfig.config_json
OutputNoneNo configuration needed

Tool plugins also support an optional per-assistant tool configuration via the config_schema class attribute on individual BaseTool subclasses. This is separate from the plugin-level config and is stored in AssistantTool.config_json. Use this when individual assistants need different settings for the same tool (e.g., a default WordPress category).

Accessing the Plugin Registry

The plugin registry is available in any Flask request or Celery task context:

from flask import current_app

registry = current_app.extensions["plugin_registry"]

# List all plugins
all_plugins = registry.get_all_plugins()

# Get plugins by type
tool_plugins = registry.get_plugins_by_type(PluginType.TOOL)

# Get a specific plugin's manifest
manifest = registry.get_plugin("my_plugin")

Tips

  • Unique names: Plugin names and tool names must be globally unique. The registry raises an error on duplicates.
  • Error handling: If a plugin fails to load (bad import, missing dependency), TeamWeb AI logs the error and continues loading other plugins. Check the application logs if your plugin doesn’t appear.
  • Dependencies: User plugins can import from app.plugins.base since the parent directory is added to sys.path during loading. For third-party packages, ensure they are installed in the application environment.
  • Logging: Use Python’s logging module. Logs from plugins appear in the normal application log output.
  • API call tracking: Always use ctx.log_api_call() in tool plugins for any external HTTP requests. This makes debugging much easier.