Plugin Development Docs
1. Plugin package structure
A plugin is a directory packaged and uploaded as a ZIP. The smallest structure is:
my_plugin/
├── plugin.json # plugin declaration (required)
├── __init__.py # an empty file is enough (required)
├── runner.py # entry logic (required)
├── provider.py # provider contract (required; may be empty for local-only plugins)
├── credentials.py # required when secrets_schema is declared
└── README.md # documentation (optional)The ZIP root must contain plugin.json directly; do not add another wrapper directory.
2. plugin.json contract
The manifest follows the loader's strict contract. The minimal example below is checked by repository contract tests; on upload, the platform rewrites plugin_id and package_id into your private namespace.
{
"plugin_id": "store.sandbox.example.text_uppercase",
"node_type": "text_uppercase",
"package_id": "store.sandbox.example.text_uppercase",
"version": "1.0.0",
"runtime_contract_version": "1.0.0",
"manifest_schema_version": "1.0.0",
"execution": "server",
"title": "文本转大写",
"category": "文本处理",
"description": "把输入文本转换为大写。",
"available_services": [
"workflow"
],
"inputs": [
{
"id": "text",
"label": "输入文本",
"data_type": "Text",
"path": "input.text"
}
],
"outputs": [
{
"id": "result",
"label": "处理结果",
"data_type": "Text",
"path": "output.result"
}
],
"config_schema": {},
"pricing_schema": {
"pricing_mode": "fixed",
"unit_credits": 1,
"unit_label": "次"
},
"secrets_schema": [],
"provider_policy": {
"provider_key": "developer.local",
"provider_type": "local",
"auth_type": "none",
"timeout_ms": 30000,
"max_retries": 0
},
"capabilities_schema": {},
"style_schema": {
"icon": "type",
"accent": "#6366f1",
"theme": "indigo",
"node_width": 420
},
"asset_schema": {},
"failure_policy": {
"refund": {
"mode": "refund_on_failure"
}
},
"permissions": {},
"metadata": {
"capabilities": [
"workflow.text_transform"
]
},
"compatibility": {
"workflow_runtime": ">=1.0.0",
"service_slug": "workflow"
},
"dependencies": {
"plugins": [],
"python": [],
"providers": []
},
"independent": true,
"entrypoint": {
"module": "runner",
"callable": "run_text_uppercase"
},
"author": {
"name": "示例开发者"
}
}Do not write ui_schema, connection_schema, output_schema, layout_schema, or display_schema by hand; the loader derives them from ports, configuration, and style declarations. Unknown fields are rejected; use the x_ prefix for extensions.
Third-party plugins currently use fixed per-call credit pricing. The current minimum price and author/platform share are defined by the current contract loaded from the Developer Center; do not implement settlement from historical numbers in this documentation.
3. runner.py entry point
The entry point is an async function. Configuration is in request["node"]["config"], and connected inputs are in request["node"]["input"]. Returns may contain only manifest outputs or standard message fields:
from typing import Any
async def run_text_uppercase(request: dict[str, Any]) -> dict[str, Any]:
node = request.get("node", {}) if isinstance(request.get("node"), dict) else {}
config = node.get("config", {}) if isinstance(node.get("config"), dict) else {}
inputs = node.get("input", {}) if isinstance(node.get("input"), dict) else {}
text = str(inputs.get("text") or config.get("text") or "")
return {
"result": text.upper(),
"message": "Completed",
"message_code": "TEXT_UPPERCASE_COMPLETED",
}Returning business fields not declared in outputs or the derived output_schema triggers a contract error. runner.py must not read platform environment variables or secrets.
4. Security limits (blocked during review)
To protect the platform and other users, plugin code may not perform the following actions; automated review blocks them:
- Import system modules such as os / sys / subprocess / socket / shutil
- Access os.environ, environment variables, or platform secrets
- Call dangerous functions such as eval / exec / open / __import__
- Read or write the local filesystem or start child processes
Allowed: httpx (HTTP calls), json / typing / datetime / re / base64 / hashlib / math / decimal / uuid / collections / itertools, plus app.core.errors and app.plugins.toolkit. Use relative imports for package modules; imports outside this list do not automatically grant platform capabilities.
User credentials such as API keys must be declared through secrets_schema and explicitly authorized in permissions.secrets; never place user secrets in config_defaults, source code, README files, or ZIP examples.
5. Submission and review flow
- Package a ZIP and upload it on theDeveloper submission wizard to a private sandbox (visible only to you)
- Run the private loading test. It reuses the production loader to validate the manifest, entry point, ports, pricing, and credential contract without executing the runner or writing to the public registry.
- Run review and submit to perform deterministic contract validation, AST capability scanning, and the Bandit high-risk scan; the flow does not include AI pre-review.
- After all checks pass, the status becomes manual_review for human review; any failed check becomes rejected and requires upload, testing, and submission again.
- After approval, the package is copied to the approved area and hot-reloaded; it becomes approved only after loading succeeds.
6. Revenue and sharing
After publication and real billable calls, call credits enter pending settlement and reach the author's balance only after the settlement cycle. Failed calls, refunds, and historical settlements follow the immutable server ledger.
The current author share, platform ratio, and minimum price are shown by the authoritative contract fields in theDeveloper Center API. This page does not copy business constants, avoiding a second source of truth when the contract changes.