Guide AI agents through Dify tool plugin development with mandatory documentation loading and direct CLI usage. Use when creating Dify tool plugins or extending Dify with custom tools.
Guide AI agents through complete Dify tool plugin development using official documentation and direct CLI commands.
Activate this skill when user:
⚠️ IMPORTANT: You (the AI agent) are responsible for:
DO NOT:
YOU execute everything. The user provides requirements and confirms results.
⚠️ BEFORE writing ANY code, you MUST load the referenced official documentation
The Dify plugin framework has STRICT requirements:
DO NOT guess, assume, or improvise. ALWAYS fetch and read the official documentation first.
To fetch official Dify documentation, use curl:
curl -s https://docs.dify.ai/plugin-dev-en/[doc-filename].md
Available Documentation (see references/doc-map.md for complete details on what each contains):
0221-initialize-development-tools.md - CLI installation and setup0222-tool-plugin.md - Complete tool plugin development guide0222-debugging-logs.md - Logging and debugging techniques 0222-tool-oauth.md - OAuth authentication implementationMANDATORY Loading Points:
0222-tool-plugin.md0222-tool-plugin.md (Developing section)0222-tool-oauth.md0222-debugging-logs.mdFollow this 5-phase workflow in sequence. Do not skip phases or documentation loading.
FIRST: Load references/doc-map.md to understand the documentation structure
Gather Requirements - Ask the user:
Determine Requirements:
Verify Dify CLI Installation:
dify version
If the CLI is not installed or returns an error:
https://docs.dify.ai/plugin-dev-en/0221-initialize-development-tools.mddify versionFor automated installation, use the provided helper script:
python scripts/install_cli.py
This script will:
Optional flags:
# Install specific version
python scripts/install_cli.py --version 0.4.0
# Install to custom directory
python scripts/install_cli.py --path ~/bin
# Both
python scripts/install_cli.py --version 0.4.0 --path /usr/local/bin
The script provides:
⚠️ BEFORE writing ANY code, load the specific documentation needed for the planned requirements.
Based on the requirements gathered in Phase 1, determine which documentation to load:
1. Tool Plugin Guide (MANDATORY for all plugins):
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-plugin.md
Read sections:
2. Debugging Guide (MANDATORY for testing):
curl -s https://docs.dify.ai/plugin-dev-en/0222-debugging-logs.md
3. OAuth Guide (IF OAuth authentication required):
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-oauth.md
Load this if the requirements include:
4. Initialize Tools (IF CLI not installed):
curl -s https://docs.dify.ai/plugin-dev-en/0221-initialize-development-tools.md
Based on Phase 1 requirements:
| Requirement | Documentation to Load |
|---|---|
| Basic tool functionality | tool-plugin.md (always) |
| API integration | tool-plugin.md + debugging-logs.md |
| OAuth authentication | tool-plugin.md + tool-oauth.md + debugging-logs.md |
| Complex logic | tool-plugin.md + debugging-logs.md + examples.md |
| CLI installation needed | initialize-development-tools.md |
⚠️ DO NOT skip this phase. Loading the wrong documentation or insufficient documentation will result in incorrect code with syntax errors.
After loading, confirm:
Only proceed to Phase 2 after loading and understanding the required documentation.
⚠️ MANDATORY: Load the tool-plugin.md documentation BEFORE proceeding
Fetch Documentation:
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-plugin.md
Read These Sections:
Initialize the Plugin:
Use the dify plugin init command with appropriate flags:
dify plugin init \
--name "your-tool-name" \
--author "author-name" \
--description "Clear, concise tool description" \
--category tool \
--language python \
--min-dify-version "1.9.0" \
--allow-network \ # Include if API access needed
--allow-storage \ # Include if storage needed
--quick # Skip interactive prompts
Available Permission Flags:
--allow-network - For external API calls--allow-storage - For persistent data storage--allow-tool - To invoke other Dify tools--allow-llm - To invoke language models--storage-size <bytes> - Specify storage limitVerify Project Structure:
ls -la your-tool-name/
Expected files and directories:
manifest.yaml - Plugin manifest configurationprovider/ - Provider definitions and credential validationtools/ - Tool implementations (YAML + Python files)main.py - Plugin entry pointrequirements.txt - Python dependencies.env.example - Environment variables templateIf any files are missing, re-run the init command or check for errors.
⚠️ MANDATORY: Re-read the tool-plugin.md "Developing the Tool Plugin" section
This section contains CRITICAL information:
tools/*.yamltools/*.pyFetch Documentation Again:
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-plugin.md
# Focus on section "Developing the Tool Plugin"
If OAuth is Required:
⚠️ MANDATORY: Load the complete OAuth documentation
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-oauth.md
Read all sections:
Implementation Steps:
tools/*.yamlFollow the exact YAML structure from the documentation:
identity:
name: tool-name # Must match filename
author: author-name
label:
en_US: Tool Display Name
zh_Hans: 工具显示名称 # Add i18n translations
pt_BR: Nome de Exibição
ja_JP: ツール表示名
description:
human:
en_US: Description for human users
zh_Hans: 人类用户的描述
llm: Detailed description for AI models to understand when to use this tool
parameters:
- name: parameter_name
type: string # string, number, boolean, file
required: true # or false for optional
label:
en_US: Parameter Display Name
zh_Hans: 参数显示名称
human_description:
en_US: Description for users
zh_Hans: 用户描述
llm_description: Detailed parameter description for AI models
form: llm # llm (AI extracts) or form (UI config)
# Add more parameters as needed
extra:
python:
source: tools/tool-name.py # Must match Python filename
Parameter Types:
string - Text inputnumber - Numeric inputboolean - True/falsefile - File uploadForm Types:
llm - AI extracts from user input (recommended for conversational use)form - User configures in UI (for admin settings)tools/*.pyFollow the exact Tool class pattern from the documentation:
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
class YourToolName(Tool):
"""
Tool for [description of what this tool does]
"""
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
"""
Invoke the tool with given parameters
Args:
tool_parameters: Dictionary of tool parameters
Yields:
ToolInvokeMessage: Messages to return to the user
"""
try:
# 1. Extract required parameters (use .get() with default)
required_param = tool_parameters.get("param_name", "")
# 2. Extract optional parameters (use .get() - returns None if missing)
optional_param = tool_parameters.get("optional_param")
# 3. Validate required parameters
if not required_param:
yield self.create_text_message("Error: Required parameter 'param_name' is missing.")
return
# 4. Implement your business logic here
result = self._process_data(required_param, optional_param)
# 5. Return results using appropriate message types
# Text message (always visible to user)
yield self.create_text_message(f"Result: {result}")
# JSON message (structured data)
yield self.create_json_message({
"status": "success",
"data": result
})
# Variable message (for workflow use)
yield self.create_variable_message("result_variable", result)
# Link message (clickable URLs)
# yield self.create_link_message("https://example.com/result")
except Exception as e:
# Always include error handling
yield self.create_text_message(f"Error: {str(e)}")
def _process_data(self, required_param: str, optional_param: str = None) -> Any:
"""
Helper method for business logic
Args:
required_param: Required input parameter
optional_param: Optional input parameter
Returns:
Processed result
"""
# Implement your logic here
# Handle optional parameters
if optional_param:
result = f"Processed with both: {required_param}, {optional_param}"
else:
result = f"Processed with required only: {required_param}"
return result
⚠️ CRITICAL RULES FROM DOCUMENTATION:
.py file - Multiple classes will cause errors_invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage].get() for parameters - Prevents KeyError exceptionsyield - Not returnMessage Types Available:
create_text_message(text) - Display text to usercreate_json_message(dict) - Return structured datacreate_link_message(url) - Return clickable linkcreate_variable_message(name, value) - Set workflow variablecreate_blob_message(data, name) - Return binary data/filesprovider/*.yamlAdd the new tool to the provider's tool list:
identity:
author: author-name
name: plugin-name
label:
en_US: Plugin Display Name
zh_Hans: 插件显示名称
description:
en_US: Plugin description
zh_Hans: 插件描述
icon: icon.svg
# Only include if API keys or credentials are needed
credentials_for_provider:
api_key:
type: secret-input
required: true
label:
en_US: API Key
zh_Hans: API 密钥
placeholder:
en_US: Enter your API key
zh_Hans: 输入您的 API 密钥
help:
en_US: Get your API key from https://example.com/api-keys
zh_Hans: 从 https://example.com/api-keys 获取您的 API 密钥
url: https://example.com/api-keys
tools:
- tools/your-tool-name.yaml # Add your tool here, it can be multiple tools in a single plugin
extra:
python:
source: provider/plugin-name.py
provider/*.pyIf credentials are needed, implement validation:
from typing import Any
from dify_plugin.entities.tool import ToolProviderCredentials
class YourPluginProvider:
def _validate_credentials(self, credentials: dict[str, Any]) -> ToolProviderCredentials:
"""
Validate provider credentials
Args:
credentials: Dictionary of credentials to validate
Returns:
ToolProviderCredentials object
Raises:
ToolProviderCredentialValidationError: If validation fails
"""
try:
# Extract credentials
api_key = credentials.get("api_key")
if not api_key:
raise Exception("API key is required")
# Validate by making a test API call
# import requests
# response = requests.get(
# "https://api.example.com/validate",
# headers={"Authorization": f"Bearer {api_key}"}
# )
# if response.status_code != 200:
# raise Exception("Invalid API key")
return ToolProviderCredentials(credentials=credentials)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
manifest.yamlComplete all internationalization fields and metadata:
version: 0.0.1 # bump this for every new version
type: plugin
author: author-name
name: plugin-name
label:
en_US: Plugin Display Name
zh_Hans: 插件显示名称
pt_BR: Nome de Exibição do Plugin
ja_JP: プラグイン表示名
description:
en_US: Detailed description of what this plugin does
zh_Hans: 该插件功能的详细描述
pt_BR: Descrição detalhada do que este plugin faz
ja_JP: このプラグインの機能の詳細な説明
icon: icon.svg
resource:
memory: 268435456 # 256MB
permission:
network:
enabled: true # If API access needed
storage:
enabled: false
size: 0
plugins:
tools:
- provider/plugin-name.yaml
meta:
version: 0.0.1
arch:
- amd64
- arm64
runner:
language: python
version: "3.12"
entrypoint: main
minimum_dify_version: 1.9.0
created_at: 2025-11-04T00:00:00.000000+00:00
privacy: PRIVACY.md
requirements.txtAdd any Python package dependencies:
dify_plugin>=0.0.1 # you must use the dify_plugin version that the CLI added when initialized.
requests>=2.31.0
# Add other dependencies as needed
Load Debugging Documentation:
curl -s https://docs.dify.ai/plugin-dev-en/0222-debugging-logs.md
Add Logging to Your Tool:
According to the documentation, add logging imports and setup:
import logging
from dify_plugin.handlers import DifyPluginLogHandler
# Set up logging
logger = logging.getLogger(__name__)
logger.addHandler(DifyPluginLogHandler())
class YourTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
logger.info(f"Tool invoked with parameters: {tool_parameters}")
# Your implementation...
logger.debug(f"Processing result: {result}")
yield self.create_text_message(result)
⚠️ DO NOT PACKAGE without completing this phase.
This phase verifies the plugin works with a real Dify instance before distribution.
Load debugging documentation:
curl -s https://docs.dify.ai/plugin-dev-en/0222-debugging-logs.md
1. Request Dify Remote Connection Details
ASK the user for remote testing connection:
"To test this plugin with your Dify instance, I need:"
1. REMOTE_INSTALL_URL - e.g., https://your-dify.com or http://localhost:5003
2. Port (if not default) - usually 5003
3. REMOTE_INSTALL_KEY - get from Dify instance developer settings
Do you have a Dify instance available for testing?
If user doesn't have a Dify instance:
2. Configure Environment
Create/update .env file with remote connection:
cd your-tool-directory
cat > .env << EOF
INSTALL_METHOD=remote
REMOTE_INSTALL_URL=debug.dify.ai:5003 # or user provided host
REMOTE_INSTALL_KEY=********-****-****-****-************
EOF
3. Start Plugin in Debug Mode
Run the plugin connected to remote host:
cd your-tool-directory
python -m main
Expected console output:
INFO: Plugin loaded successfully
INFO: Connected to Dify remote host: https://your-dify-instance.com
INFO: Registered tools: [tool-name]
INFO: Waiting for invocations...
If errors appear:
4. Test Tool Invocation from Dify
In Dify UI:
In console (watch logs):
INFO: Tool invoked: your-tool-name
DEBUG: Parameters: {"param1": "value1"}
DEBUG: Processing...
DEBUG: API call successful
INFO: Returned result to Dify
5. Test All Scenarios
Run comprehensive tests:
Test Case 1: Valid Input
Input: Normal, expected parameters
Expected: Success, correct output
Test Case 2: Missing Required Parameter
Input: Omit required parameter
Expected: Error message, graceful handling
Test Case 3: Invalid Input
Input: Wrong type or format
Expected: Validation error, helpful message
Test Case 4: API Integration (if applicable)
Input: Valid request requiring API call
Expected: Successful API call, correct data returned
Test Case 5: Error Conditions
Input: Trigger expected errors (API timeout, invalid credentials, etc.)
Expected: Proper error handling, helpful messages
Test Case 6: OAuth Flow (if applicable)
Action: Complete OAuth authorization
Expected: Token received, API calls work with token
Expected Output:
If Errors Occur:
references/troubleshooting.mdCommon Errors:
| Error | Cause | Solution |
|---|---|---|
Multiple subclasses of Tool in file.py |
Multiple Tool classes in one file | Keep only one Tool class per .py file, move others to new files |
ImportError: cannot import name 'X' |
Import name doesn't match definition | Check spelling, case, underscores in import statements |
KeyError: 'parameter_name' |
Parameter accessed without checking | Use .get() method: param = tool_parameters.get("name", "") |
ToolProviderCredentialValidationError |
Credential validation failed | Check API key format, test API endpoint, verify credentials |
Debug Checklist:
.get() for all parameters6. Verify Debug Logging
Check that logs show:
Example good logging:
logger.info(f"Tool invoked: {tool_name}")
logger.debug(f"Parameters: {tool_parameters}")
logger.debug("Calling API endpoint...")
logger.debug(f"API response status: {response.status_code}")
logger.info("Successfully processed request")
7. Debug Issues
If tool doesn't work as expected:
references/troubleshooting.md for common issuesCommon remote testing issues:
| Issue | Cause | Solution |
|---|---|---|
| Plugin doesn't appear | Registration failed | Check manifest.yaml, restart plugin |
| Connection refused | Wrong host URL | Verify DIFY_REMOTE_HOST |
| Unauthorized | Invalid debug key | Check DIFY_DEBUG_KEY |
| Tool fails | Parameter handling | Verify .get() usage, check logs |
| API errors | Credential issues | Test API key separately |
8. User Confirmation
Before proceeding to packaging, ask user:
"I've tested the plugin with your Dify instance. Results:
✅ Plugin loaded successfully
✅ Tool appears in Dify UI
✅ Test invocations working
✅ [List specific test results]
Does everything work as expected? Any issues or adjustments needed?"
Wait for user confirmation before packaging.
⚠️ CRITICAL CHECK: Verify the dify_plugin version that CLI generated.
The Dify CLI always creates requirements.txt with the latest dify_plugin version.
cat your-tool-name/requirements.txt
Check what version was generated:
dify_plugin>=X.X.X
DO NOT override this version. The CLI uses the latest compatible version.
Note the version for reference:
Example:
dify_plugin>=2.4.2 # Generated by CLI - KEEP THIS
requests>=2.31.0 # Add if you need requests
pyyaml>=6.0 # Add if you need yaml
⚠️ If ANY criterion fails:
Load Packaging Documentation:
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-plugin.md
# Read the "Packaging the Plugin" section
Package the Plugin:
dify plugin package ./your-tool-directory
This creates a .difypkg file in the current directory.
Expected Output:
✓ plugin packaged successfully, output path: your-tool-name.difypkg
Verify Package:
ls -lh *.difypkg
The file should exist and have a reasonable size (typically 10-100 KB for simple plugins).
Publishing (Optional):
If the user wants to publish to the Dify marketplace:
.difypkg fileGuide the user through this process if requested.
If the tool requires OAuth authentication (e.g., GitHub, Google, Slack):
⚠️ MANDATORY: Load the complete OAuth documentation BEFORE implementing
curl -s https://docs.dify.ai/plugin-dev-en/0222-tool-oauth.md
OAuth Implementation Steps:
provider/*.yaml:oauth_schema:
client:
- name: client_id
type: string
required: true
label:
en_US: Client ID
- name: client_secret
type: secret-input
required: true
label:
en_US: Client Secret
authorization:
url: https://provider.com/oauth/authorize
scopes:
- scope1
- scope2
token:
url: https://provider.com/oauth/token
provider/*.py:def get_authorization_url(self, credentials: dict) -> str:
"""Return OAuth authorization URL"""
pass
def validate_oauth_callback(self, credentials: dict, state: str, code: str) -> dict:
"""Exchange code for access token"""
pass
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
# Access token is available in runtime
access_token = self.runtime.credentials.get("access_token")
# Use token in API calls
headers = {"Authorization": f"Bearer {access_token}"}
Refer to the OAuth documentation for complete implementation details.
ALWAYS:
.get() method for accessing parametersNEVER:
.get() or validationSecurity Considerations:
Throughout the development process, keep the user informed:
Example Progress Update:
✅ Phase 1 Complete: Requirements gathered
✅ Phase 2 Complete: Project initialized
🔄 Phase 3 In Progress: Implementing tool (60% done)
✅ YAML configuration complete
✅ Tool class structure created
🔄 Adding API integration
⏳ Error handling pending
⏳ Logging setup pending
⏳ Phase 4 Pending: Testing & debugging
⏳ Phase 5 Pending: Packaging
Load these reference files when you need specific information:
references/doc-map.md - Complete map of what each official doc containsreferences/workflow.md - Detailed 5-phase workflowreferences/cli-commands.md - Dify CLI command referencereferences/troubleshooting.md - Common errors and solutionsreferences/examples.md - Real-world plugin examplesHelper Script:
Use the fetch script to quickly retrieve official docs:
bash scripts/fetch_doc.sh tool # Fetch tool-plugin.md
bash scripts/fetch_doc.sh oauth # Fetch tool-oauth.md
bash scripts/fetch_doc.sh debug # Fetch debugging-logs.md
bash scripts/fetch_doc.sh init # Fetch initialize-development-tools.md
This skill guides you through a structured 5-phase workflow:
Key Success Factors:
The result will be a production-ready Dify tool plugin that follows best practices and official guidelines.