AutoGPT Plugin System: Extend with Custom Commands
Learn how to build custom AutoGPT plugins, register commands, use the plugin base class, and extend your agent with domain-specific capabilities beyond the defaults.
Get more content like this on Telegram!
Daily AI tips, notes & resources ā free
AutoGPT's built-in commands cover the basics ā search, browse, read files, write files, run Python. But the moment you need something domain-specific ā query a database, send a Slack message, call your company's internal API, scrape a specific website with custom parsing ā you hit the edge of what the defaults provide.
The plugin system is how you extend AutoGPT beyond those defaults. A plugin is a Python class that registers new commands, intercepts messages, or modifies agent behavior at key points in the execution loop. Once registered, the agent can call your custom commands exactly like it calls built-in ones.
For context on how AutoGPT handles goals and commands more broadly, AutoGPT prompts and goals guide covers the goal and command structure that plugins extend. And for alternative extension approaches, Build AI agent with LangChain shows how LangChain's tool system achieves similar goals with a different API.
How the Plugin System Works
AutoGPT loads plugins at startup by scanning the plugins/ directory. Any class that inherits from AutoGPTPluginTemplate and implements the required interface gets loaded. The agent's available command list is populated from all loaded plugins plus the built-in commands.
Here is the execution flow:
AutoGPT startup
āāā Scans plugins/ directory
āāā Loads classes inheriting AutoGPTPluginTemplate
āāā Registers plugin commands in command registry
āāā Agent can now call plugin commands
When the agent decides to use a command, AutoGPT routes it through the command registry, which dispatches to the appropriate plugin method.
Setting Up the Plugin Directory
AutoGPT/
āāā autogpts/
ā āāā autogpt/
ā āāā plugins/ ā your plugins go here
ā ā āāā __init__.py
ā ā āāā my_plugin.py ā single-file plugin
ā ā āāā my_package/ ā package-style plugin
ā ā āāā __init__.py
ā ā āāā plugin.py
ā āāā .env
The AutoGPTPluginTemplate Base Class
# autogpts/autogpt/plugins/template.py (from AutoGPT source)
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Tuple, TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
from autogpt.config import Config
from autogpt.models.command import Command
PromptGenerator = TypeVar("PromptGenerator")
class AutoGPTPluginTemplate(ABC):
"""Base class for all AutoGPT plugins."""
_version: str = "0.1.0"
_name: str = ""
_description: str = ""
def __init__(self):
pass
@abstractmethod
def can_handle_on_response(self) -> bool:
"""Whether this plugin handles LLM response events."""
...
@abstractmethod
def on_response(self, response: str, *args, **kwargs) -> str:
"""Process LLM response before it is acted on."""
...
@abstractmethod
def can_handle_post_prompt(self) -> bool:
"""Whether this plugin modifies the prompt after base construction."""
...
@abstractmethod
def post_prompt(self, prompt: PromptGenerator) -> PromptGenerator:
"""Modify the prompt generator."""
...
@abstractmethod
def can_handle_on_planning(self) -> bool:
"""Whether this plugin participates in the planning phase."""
...
@abstractmethod
def on_planning(
self, prompt: PromptGenerator, messages: List[Dict[str, Any]]
) -> Optional[str]:
"""Hook into the planning phase."""
...
@abstractmethod
def can_handle_post_planning(self) -> bool:
...
@abstractmethod
def post_planning(self, response: str) -> str:
...
@abstractmethod
def can_handle_pre_instruction(self) -> bool:
...
@abstractmethod
def pre_instruction(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
...
@abstractmethod
def can_handle_on_instruction(self) -> bool:
...
@abstractmethod
def on_instruction(self, messages: List[Dict[str, Any]]) -> Optional[str]:
...
@abstractmethod
def can_handle_post_instruction(self) -> bool:
...
@abstractmethod
def post_instruction(self, response: str) -> str:
...
@abstractmethod
def can_handle_pre_command(self) -> bool:
...
@abstractmethod
def pre_command(
self, command_name: str, arguments: Dict[str, Any]
) -> Tuple[str, Dict[str, Any]]:
...
@abstractmethod
def can_handle_post_command(self) -> bool:
...
@abstractmethod
def post_command(self, command_name: str, response: str) -> str:
...
@abstractmethod
def can_handle_chat_completion(
self,
messages: Dict[Any, Any],
model: str,
temperature: float,
max_tokens: int,
) -> bool:
...
@abstractmethod
def handle_chat_completion(
self,
messages: List[Dict[str, Any]],
model: str,
temperature: float,
max_tokens: int,
) -> str:
...
This is the full interface. In practice, most plugins only use a few of these hooks and return False for the rest.
Building a Real Plugin: Slack Notifier
Here is a complete plugin that adds a send_slack_message command:
# plugins/slack_plugin.py
import os
from typing import Any, Dict, List, Optional, Tuple
from autogpt.plugins import AutoGPTPluginTemplate
try:
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
SLACK_AVAILABLE = True
except ImportError:
SLACK_AVAILABLE = False
class SlackNotifierPlugin(AutoGPTPluginTemplate):
"""
AutoGPT plugin for sending Slack notifications.
Adds the 'send_slack_message' command to AutoGPT.
"""
_name = "SlackNotifierPlugin"
_version = "1.0.0"
_description = "Send messages to Slack channels from AutoGPT"
def __init__(self):
super().__init__()
self._token = os.getenv("SLACK_BOT_TOKEN")
self._default_channel = os.getenv("SLACK_DEFAULT_CHANNEL", "#general")
if SLACK_AVAILABLE and self._token:
self._client = WebClient(token=self._token)
self._enabled = True
else:
self._client = None
self._enabled = False
print(f"[{self._name}] Slack SDK not installed or SLACK_BOT_TOKEN not set. Plugin disabled.")
# āā Command registration āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def can_handle_post_prompt(self) -> bool:
return self._enabled
def post_prompt(self, prompt) -> Any:
"""Register the send_slack_message command with AutoGPT."""
if self._enabled:
prompt.add_command(
command_label="send_slack_message",
command_name="send_slack_message",
params={
"message": "<message_text>",
"channel": "<#channel_name or leave empty for default>",
},
function=self._send_slack_message,
)
return prompt
# āā Command implementation āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def _send_slack_message(self, message: str, channel: str = "") -> str:
"""Send a message to a Slack channel."""
if not self._enabled:
return "Error: Slack plugin is not configured."
target_channel = channel if channel else self._default_channel
try:
response = self._client.chat_postMessage(
channel=target_channel,
text=message,
)
return f"Message sent to {target_channel}. Timestamp: {response['ts']}"
except SlackApiError as e:
return f"Slack API error: {e.response['error']}"
except Exception as e:
return f"Error sending message: {str(e)}"
# āā Required hook implementations (pass-through) āāāāāāāāāāāāāāāāāāāāāāāāāā
def can_handle_on_response(self) -> bool:
return False
def on_response(self, response: str, *args, **kwargs) -> str:
return response
def can_handle_on_planning(self) -> bool:
return False
def on_planning(self, prompt, messages) -> Optional[str]:
return None
def can_handle_post_planning(self) -> bool:
return False
def post_planning(self, response: str) -> str:
return response
def can_handle_pre_instruction(self) -> bool:
return False
def pre_instruction(self, messages) -> List[Dict]:
return messages
def can_handle_on_instruction(self) -> bool:
return False
def on_instruction(self, messages) -> Optional[str]:
return None
def can_handle_post_instruction(self) -> bool:
return False
def post_instruction(self, response: str) -> str:
return response
def can_handle_pre_command(self) -> bool:
return False
def pre_command(self, command_name: str, arguments: Dict) -> Tuple[str, Dict]:
return command_name, arguments
def can_handle_post_command(self) -> bool:
return False
def post_command(self, command_name: str, response: str) -> str:
return response
def can_handle_chat_completion(self, messages, model, temperature, max_tokens) -> bool:
return False
def handle_chat_completion(self, messages, model, temperature, max_tokens) -> str:
return None
Installation:
# Install the Slack SDK
pip install slack-sdk
# Add to .env
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_DEFAULT_CHANNEL=#autogpt-notifications
# The plugin file is already in plugins/
# AutoGPT picks it up on next startup
Building a Database Query Plugin
A more practical example ā querying a PostgreSQL database:
# plugins/database_plugin.py
import os
import json
from typing import Any, Dict, List, Optional, Tuple
from autogpt.plugins import AutoGPTPluginTemplate
try:
import psycopg2
import psycopg2.extras
DB_AVAILABLE = True
except ImportError:
DB_AVAILABLE = False
class DatabaseQueryPlugin(AutoGPTPluginTemplate):
"""
Plugin for querying databases from AutoGPT.
Adds 'query_database' and 'list_tables' commands.
"""
_name = "DatabaseQueryPlugin"
_version = "1.0.0"
_description = "Execute SQL queries against a configured database"
def __init__(self):
super().__init__()
self._db_url = os.getenv("DATABASE_URL")
self._enabled = DB_AVAILABLE and bool(self._db_url)
def can_handle_post_prompt(self) -> bool:
return self._enabled
def post_prompt(self, prompt) -> Any:
if not self._enabled:
return prompt
prompt.add_command(
command_label="query_database",
command_name="query_database",
params={
"sql": "<SELECT statement to execute>",
"limit": "<max rows to return, default 10>",
},
function=self._query_database,
)
prompt.add_command(
command_label="list_tables",
command_name="list_tables",
params={},
function=self._list_tables,
)
return prompt
def _query_database(self, sql: str, limit: int = 10) -> str:
"""Execute a SELECT query and return results as JSON."""
# Safety: only allow SELECT statements
sql_clean = sql.strip().upper()
if not sql_clean.startswith("SELECT"):
return "Error: Only SELECT queries are permitted."
# Add LIMIT if not present
if "LIMIT" not in sql_clean:
sql = f"{sql.rstrip(';')} LIMIT {limit}"
try:
conn = psycopg2.connect(self._db_url)
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
cur.execute(sql)
rows = cur.fetchall()
cur.close()
conn.close()
if not rows:
return "Query returned no results."
result = [dict(row) for row in rows]
return json.dumps(result, indent=2, default=str)
except psycopg2.Error as e:
return f"Database error: {e.pgerror}"
except Exception as e:
return f"Error: {str(e)}"
def _list_tables(self) -> str:
"""List all tables in the database."""
try:
conn = psycopg2.connect(self._db_url)
cur = conn.cursor()
cur.execute("""
SELECT table_name, table_type
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name
""")
tables = cur.fetchall()
cur.close()
conn.close()
if not tables:
return "No tables found in database."
table_list = "\n".join([f" - {t[0]} ({t[1]})" for t in tables])
return f"Available tables:\n{table_list}"
except Exception as e:
return f"Error listing tables: {str(e)}"
# Required pass-through implementations
def can_handle_on_response(self) -> bool: return False
def on_response(self, response, *args, **kwargs): return response
def can_handle_on_planning(self) -> bool: return False
def on_planning(self, prompt, messages): return None
def can_handle_post_planning(self) -> bool: return False
def post_planning(self, response): return response
def can_handle_pre_instruction(self) -> bool: return False
def pre_instruction(self, messages): return messages
def can_handle_on_instruction(self) -> bool: return False
def on_instruction(self, messages): return None
def can_handle_post_instruction(self) -> bool: return False
def post_instruction(self, response): return response
def can_handle_pre_command(self) -> bool: return False
def pre_command(self, command_name, arguments): return command_name, arguments
def can_handle_post_command(self) -> bool: return False
def post_command(self, command_name, response): return response
def can_handle_chat_completion(self, messages, model, temperature, max_tokens): return False
def handle_chat_completion(self, messages, model, temperature, max_tokens): return None
A Plugin Base Class to Reduce Boilerplate
The repeated pass-through methods are tedious. Here is a minimal base class that handles them:
# plugins/base.py ā your own minimal base
from typing import Any, Dict, List, Optional, Tuple
from autogpt.plugins import AutoGPTPluginTemplate
class MinimalPlugin(AutoGPTPluginTemplate):
"""
AutoGPT plugin base with sensible defaults.
Override only the methods you need.
"""
_name = "MinimalPlugin"
_version = "1.0.0"
_description = "Base plugin with default implementations"
def can_handle_on_response(self) -> bool: return False
def on_response(self, response, *args, **kwargs): return response
def can_handle_post_prompt(self) -> bool: return False
def post_prompt(self, prompt): return prompt
def can_handle_on_planning(self) -> bool: return False
def on_planning(self, prompt, messages): return None
def can_handle_post_planning(self) -> bool: return False
def post_planning(self, response): return response
def can_handle_pre_instruction(self) -> bool: return False
def pre_instruction(self, messages): return messages
def can_handle_on_instruction(self) -> bool: return False
def on_instruction(self, messages): return None
def can_handle_post_instruction(self) -> bool: return False
def post_instruction(self, response): return response
def can_handle_pre_command(self) -> bool: return False
def pre_command(self, cmd, args): return cmd, args
def can_handle_post_command(self) -> bool: return False
def post_command(self, cmd, response): return response
def can_handle_chat_completion(self, messages, model, temp, max_tokens): return False
def handle_chat_completion(self, messages, model, temp, max_tokens): return None
Now your plugins only need to override what they actually use:
# plugins/weather_plugin.py
import os
import requests
from .base import MinimalPlugin # inherit from your minimal base
class WeatherPlugin(MinimalPlugin):
"""Get weather data for any location."""
_name = "WeatherPlugin"
_version = "1.0.0"
_description = "Fetch current weather and forecasts"
def __init__(self):
super().__init__()
self._api_key = os.getenv("OPENWEATHER_API_KEY")
self._enabled = bool(self._api_key)
def can_handle_post_prompt(self) -> bool:
return self._enabled
def post_prompt(self, prompt):
prompt.add_command(
command_label="get_weather",
command_name="get_weather",
params={"location": "<city name or lat,lon>"},
function=self._get_weather,
)
return prompt
def _get_weather(self, location: str) -> str:
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": location,
"appid": self._api_key,
"units": "metric",
}
try:
response = requests.get(url, params=params, timeout=10)
data = response.json()
if response.status_code == 200:
temp = data["main"]["temp"]
desc = data["weather"][0]["description"]
humidity = data["main"]["humidity"]
return f"Weather in {location}: {temp}°C, {desc}, humidity {humidity}%"
else:
return f"Weather API error: {data.get('message', 'Unknown error')}"
except Exception as e:
return f"Error fetching weather: {str(e)}"
Available Plugins in the AutoGPT Ecosystem
| Plugin | Function | Install Required |
|---|---|---|
| AutoGPT-Google | Google Search with SerpAPI | serpapi |
| AutoGPT-Bing-Search | Bing search results | requests |
| AutoGPT-Twitter | Post and read tweets | tweepy |
| AutoGPT-YouTube | Search and fetch video info | youtube-dl |
| AutoGPT-Wikipedia | Wikipedia article search | wikipedia |
| AutoGPT-GitHub | Interact with GitHub repos | PyGithub |
| AutoGPT-Notion | Read/write Notion pages | notion-client |
| AutoGPT-Email | Send and read emails | smtplib (stdlib) |
| AutoGPT-WolframAlpha | Math and factual queries | wolframalpha |
| AutoGPT-Stable-Diffusion | Image generation | stability-sdk |
Install community plugins:
# Most community plugins follow this pattern
cd AutoGPT/autogpts/autogpt/plugins
git clone https://github.com/author/AutoGPT-PluginName
# Install dependencies
pip install -r plugins/AutoGPT-PluginName/requirements.txt
# Add required env vars to .env
echo "PLUGIN_API_KEY=your_key" >> .env
Using the pre_command Hook for Command Logging
The pre_command and post_command hooks let you intercept all command calls, not just your own. This is useful for logging, rate limiting, or validating arguments:
class CommandLoggerPlugin(MinimalPlugin):
"""Log all AutoGPT command calls to a file."""
_name = "CommandLogger"
_version = "1.0.0"
_description = "Logs all command calls for audit and debugging"
def __init__(self):
super().__init__()
self._log_file = "command_log.jsonl"
def can_handle_pre_command(self) -> bool:
return True
def pre_command(self, command_name: str, arguments: dict) -> tuple:
"""Log every command before execution."""
import json
from datetime import datetime
entry = {
"timestamp": datetime.now().isoformat(),
"command": command_name,
"arguments": arguments,
}
with open(self._log_file, "a") as f:
f.write(json.dumps(entry) + "\n")
# Return unchanged ā we are only logging, not modifying
return command_name, arguments
def can_handle_post_command(self) -> bool:
return True
def post_command(self, command_name: str, response: str) -> str:
"""Log command result."""
import json
from datetime import datetime
entry = {
"timestamp": datetime.now().isoformat(),
"command": command_name,
"result_preview": response[:200],
}
with open(self._log_file, "a") as f:
f.write(json.dumps(entry) + "\n")
return response # return unchanged
Enabling and Disabling Plugins
AutoGPT respects the ALLOWLISTED_PLUGINS and DENYLISTED_PLUGINS environment variables:
# .env ā enable only specific plugins
ALLOWLISTED_PLUGINS=SlackNotifierPlugin,DatabaseQueryPlugin,WeatherPlugin
# OR disable specific plugins
DENYLISTED_PLUGINS=CommandLoggerPlugin
# To disable all plugins
ALLOWLISTED_PLUGINS=
This gives you control over which capabilities the agent has access to in each run. A research-focused run might enable web search and file writing but disable Slack and database plugins to reduce the attack surface.
For comparison with how tool capabilities work in other frameworks, AutoGen agent roles and types shows AutoGen's approach to extending agent capabilities, and AutoGPT forks and alternatives shows which alternative frameworks handle extensions differently.
Plugin Development Checklist
Before deploying a plugin to a shared AutoGPT instance:
- Store all secrets in
.env, never hardcode them - Add input validation to every command function ā treat all agent inputs as untrusted
- Limit database plugins to SELECT-only (no INSERT, UPDATE, DELETE)
- Add timeouts to all external API calls
- Log command calls and results for audit purposes
- Test with
human_input_mode="ALWAYS"first to review every command call manually - Set
CONTINUOUS_LIMITto a low number for the first few runs
The plugin system is powerful specifically because it gives you direct access to the command execution layer. That same access is what makes validation and sandboxing important.
For production deployments that involve custom plugins alongside external APIs, Deploy AI model to production and OpenAI API integration cover the infrastructure and API management side.
Frequently Asked Questions
Where do AutoGPT plugins go in the project directory? AutoGPT plugins are placed in the plugins/ directory at the root of your AutoGPT installation. Each plugin is either a Python module (single .py file) or a package (directory with init.py). AutoGPT scans this directory on startup and loads any class that inherits from AutoGPTPluginTemplate.
Can I use external APIs in AutoGPT plugins? Yes. AutoGPT plugins are plain Python classes, so you can import any library and call any external API. Just install the required packages (pip install your_package) and import them in your plugin. For API keys, store them in the .env file and access them with os.getenv().
What is the difference between a plugin command and a tool in AutoGen? AutoGPT plugin commands are registered as discrete actions the agent can choose from its command list. AutoGen tools are Python functions decorated or wrapped as tools that agents can call via tool use. Conceptually similar, but AutoGPT plugins use a class-based registration system while AutoGen tools use function-based wrapping.
Frequently Asked Questions
AiTechWorlds Team
ā Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
10 AutoGPT Command Line Arguments (Continuous Mode, Speak)
Complete reference for AutoGPT's 10 most powerful CLI arguments. Master continuous mode, headless operation, and CI/CD integration for automated agent workflows.
10 AutoGPT Configuration Tweaks for Better Performance
10 proven AutoGPT configuration tweaks to improve speed, cut costs, and boost task success. Model selection, temperature, token limits, and workspace settings.
Build a Content Research Agent with AutoGPT (Trends, Outlines)
Build an AutoGPT content research agent that finds trending topics, analyzes SERPs, and generates SEO-ready outlines automatically ā full workflow inside.
Build a Data Analysis Agent with AutoGPT (CSV, SQL, Plots)
Build a data analysis agent using AutoGPT that reads CSVs, queries SQL databases, and generates plots automatically. Full code with pandas and matplotlib.