Building plugins
ساخت Pluginها
Pluginها OpenClaw را با قابلیتهای تازه گسترش میدهند: کانالها، ارائهدهندگان مدل، گفتار، رونویسی بلادرنگ، صدای بلادرنگ، درک رسانه، تولید تصویر، تولید ویدئو، دریافت وب، جستوجوی وب، ابزارهای عامل، یا هر ترکیبی از آنها.
لازم نیست Plugin خود را به مخزن OpenClaw اضافه کنید. در
ClawHub منتشر کنید و کاربران با
openclaw plugins install clawhub:<package-name> نصب میکنند. مشخصات خام بسته همچنان
در دوره گذار راهاندازی از npm نصب میشوند.
پیشنیازها
- Node >= 22 و یک مدیر بسته (npm یا pnpm)
- آشنایی با TypeScript (ESM)
- برای Pluginهای داخل مخزن: مخزن clone شده و
pnpm installانجام شده باشد. توسعه Plugin با checkout منبع فقط با pnpm است، چون OpenClaw، Pluginهای بستهبندیشده را از بستههای workspace درextensions/*بارگذاری میکند.
چه نوع Pluginی؟
OpenClaw را به یک سکوی پیامرسانی وصل کنید (Discord، IRC و غیره)
یک ارائهدهنده مدل اضافه کنید (LLM، پراکسی، یا endpoint سفارشی)
یک CLI هوش مصنوعی محلی را به اجراکننده fallback متنی OpenClaw نگاشت کنید
ابزارهای عامل، hookهای رویداد، یا سرویسها را ثبت کنید - در ادامه بخوانید
برای یک Plugin کانال که تضمین نمیشود هنگام اجرای onboarding/setup نصب شده باشد،
از createOptionalChannelSetupSurface(...) در
openclaw/plugin-sdk/channel-setup استفاده کنید. این یک جفت آداپتور setup + wizard
میسازد که نیاز نصب را اعلام میکند و تا وقتی Plugin نصب نشده باشد، در نوشتن config واقعی
بسته شکست میخورد.
شروع سریع: Plugin ابزار
این راهنما یک Plugin حداقلی میسازد که یک ابزار عامل را ثبت میکند. Pluginهای کانال و ارائهدهنده راهنماهای اختصاصی دارند که بالاتر لینک شدهاند.
Create the package and manifest
{"name": "@myorg/openclaw-my-plugin","version": "1.0.0","type": "module","openclaw": { "extensions": ["./index.ts"], "compat": { "pluginApi": ">=2026.3.24-beta.2", "minGatewayVersion": "2026.3.24-beta.2" }, "build": { "openclawVersion": "2026.3.24-beta.2", "pluginSdkVersion": "2026.3.24-beta.2" }}}{"id": "my-plugin","name": "My Plugin","description": "Adds a custom tool to OpenClaw","contracts": { "tools": ["my_tool"]},"activation": { "onStartup": true},"configSchema": { "type": "object", "additionalProperties": false}}هر Plugin به یک manifest نیاز دارد، حتی اگر config نداشته باشد. ابزارهایی که در runtime ثبت میشوند
باید در contracts.tools فهرست شوند تا OpenClaw بتواند Plugin مالک را بدون بارگذاری
runtime همه Pluginها کشف کند. Pluginها همچنین باید activation.onStartup را آگاهانه اعلام کنند.
این مثال آن را روی true تنظیم میکند. برای schema کامل، Manifest را ببینید. snippetهای canonical انتشار ClawHub
در docs/snippets/plugin-publish/ قرار دارند.
Write the entry point
// index.tsimport { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";import { Type } from "@sinclair/typebox"; export default definePluginEntry({ id: "my-plugin", name: "My Plugin", description: "Adds a custom tool to OpenClaw", register(api) { api.registerTool({ name: "my_tool", description: "Do a thing", parameters: Type.Object({ input: Type.String() }), async execute(_id, params) { return { content: [{ type: "text", text: `Got: ${params.input}` }] }; }, }); },});definePluginEntry برای Pluginهای غیرکانالی است. برای کانالها، از
defineChannelPluginEntry استفاده کنید - Channel Plugins را ببینید.
برای گزینههای کامل entry point، Entry Points را ببینید.
Test and publish
Pluginهای خارجی: با ClawHub اعتبارسنجی و منتشر کنید، سپس نصب کنید:
clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-pluginopenclaw plugins install clawhub:@myorg/openclaw-my-pluginمشخصات خام بسته مانند @myorg/openclaw-my-plugin در دوره گذار راهاندازی از npm نصب میشوند.
وقتی resolve از ClawHub را میخواهید، از clawhub: استفاده کنید.
Pluginهای داخل مخزن: زیر درخت workspace Pluginهای بستهبندیشده قرار دهید - خودکار کشف میشود.
pnpm test -- <bundled-plugin-root>/my-plugin/قابلیتهای Plugin
یک Plugin میتواند از طریق شیء api هر تعداد قابلیت را ثبت کند:
| قابلیت | روش ثبت | راهنمای تفصیلی |
|---|---|---|
| استنتاج متن (LLM) | api.registerProvider(...) |
Provider Plugins |
| backend استنتاج CLI | api.registerCliBackend(...) |
CLI Backend Plugins |
| کانال / پیامرسانی | api.registerChannel(...) |
Channel Plugins |
| گفتار (TTS/STT) | api.registerSpeechProvider(...) |
Provider Plugins |
| رونویسی بلادرنگ | api.registerRealtimeTranscriptionProvider(...) |
Provider Plugins |
| صدای بلادرنگ | api.registerRealtimeVoiceProvider(...) |
Provider Plugins |
| درک رسانه | api.registerMediaUnderstandingProvider(...) |
Provider Plugins |
| تولید تصویر | api.registerImageGenerationProvider(...) |
Provider Plugins |
| تولید موسیقی | api.registerMusicGenerationProvider(...) |
Provider Plugins |
| تولید ویدئو | api.registerVideoGenerationProvider(...) |
Provider Plugins |
| دریافت وب | api.registerWebFetchProvider(...) |
Provider Plugins |
| جستوجوی وب | api.registerWebSearchProvider(...) |
Provider Plugins |
| middleware نتیجه ابزار | api.registerAgentToolResultMiddleware(...) |
SDK Overview |
| ابزارهای عامل | api.registerTool(...) |
پایین |
| commandهای سفارشی | api.registerCommand(...) |
Entry Points |
| hookهای Plugin | api.on(...) |
Plugin hooks |
| hookهای رویداد داخلی | api.registerHook(...) |
Entry Points |
| مسیرهای HTTP | api.registerHttpRoute(...) |
Internals |
| subcommandهای CLI | api.registerCli(...) |
Entry Points |
برای API کامل ثبت، SDK Overview را ببینید.
Pluginهای بستهبندیشده میتوانند وقتی به بازنویسی async نتیجه ابزار پیش از دیده شدن خروجی توسط مدل
نیاز دارند، از api.registerAgentToolResultMiddleware(...) استفاده کنند. runtimeهای هدف را در
contracts.agentToolResultMiddleware اعلام کنید، برای مثال
["pi", "codex"]. این یک seam مورد اعتماد برای Pluginهای بستهبندیشده است؛ Pluginهای خارجی
باید hookهای معمول OpenClaw Plugin را ترجیح دهند، مگر اینکه OpenClaw برای این قابلیت
یک سیاست اعتماد صریح اضافه کند.
اگر Plugin شما روشهای RPC سفارشی Gateway ثبت میکند، آنها را روی یک پیشوند
اختصاصی Plugin نگه دارید. namespaceهای ادمین core (config.*,
exec.approvals.*, wizard.*, update.*) رزرو میمانند و همیشه به
operator.admin resolve میشوند، حتی اگر یک Plugin scope محدودتری بخواهد.
معنای guard hookها که باید در نظر داشته باشید:
before_tool_call:{ block: true }نهایی است و handlerهای با اولویت پایینتر را متوقف میکند.before_tool_call:{ block: false }بهعنوان نبود تصمیم تلقی میشود.before_tool_call:{ requireApproval: true }اجرای عامل را pause میکند و از کاربر از طریق overlay تأیید exec، دکمههای Telegram، interactionهای Discord، یا command/approveدر هر کانالی درخواست تأیید میکند.before_install:{ block: true }نهایی است و handlerهای با اولویت پایینتر را متوقف میکند.before_install:{ block: false }بهعنوان نبود تصمیم تلقی میشود.message_sending:{ cancel: true }نهایی است و handlerهای با اولویت پایینتر را متوقف میکند.message_sending:{ cancel: false }بهعنوان نبود تصمیم تلقی میشود.message_received: وقتی به routing thread/topic ورودی نیاز دارید، فیلد typedthreadIdرا ترجیح دهید.metadataرا برای موارد اضافی مختص کانال نگه دارید.message_sending: فیلدهای routing typed یعنیreplyToId/threadIdرا به کلیدهای metadata مختص کانال ترجیح دهید.
command /approve هم تأییدهای exec و هم Plugin را با fallback محدود مدیریت میکند: وقتی id تأیید exec پیدا نشود، OpenClaw همان id را در تأییدهای Plugin دوباره امتحان میکند. forwarding تأیید Plugin را میتوان بهطور مستقل از طریق approvals.plugin در config پیکربندی کرد.
اگر plumbing تأیید سفارشی نیاز دارد همان حالت fallback محدود را تشخیص دهد،
بهجای matching دستی رشتههای انقضای تأیید، isApprovalNotFoundError را از
openclaw/plugin-sdk/error-runtime ترجیح دهید.
برای مثالها و مرجع hook، Plugin hooks را ببینید.
ثبت ابزارهای عامل
ابزارها توابع typed هستند که LLM میتواند فراخوانی کند. آنها میتوانند الزامی باشند (همیشه در دسترس) یا اختیاری باشند (opt-in کاربر):
register(api) { // Required tool - always available api.registerTool({ name: "my_tool", description: "Do a thing", parameters: Type.Object({ input: Type.String() }), async execute(_id, params) { return { content: [{ type: "text", text: params.input }] }; }, }); // Optional tool - user must add to allowlist api.registerTool( { name: "workflow_tool", description: "Run a workflow", parameters: Type.Object({ pipeline: Type.String() }), async execute(_id, params) { return { content: [{ type: "text", text: params.pipeline }] }; }, }, { optional: true }, );}کارخانههای ابزار یک شیء زمینهای تامینشده توسط زمان اجرا دریافت میکنند. وقتی یک ابزار نیاز دارد مدل فعال در نوبت فعلی را ثبت، نمایش، یا خود را با آن سازگار کند، از
ctx.activeModel استفاده کنید. این شیء میتواند شامل provider، modelId و
modelRef باشد. آن را فرادادهٔ اطلاعاتی زمان اجرا در نظر بگیرید، نه یک مرز امنیتی
در برابر اپراتور محلی، کد Plugin نصبشده، یا زمان اجرای تغییریافتهٔ
OpenClaw. برای ابزارهای محلی حساس، opt-in صریح Plugin یا اپراتور را نگه دارید
و وقتی فرادادهٔ مدل فعال موجود نیست یا مناسب نیست، بهصورت بسته شکست بخورید.
هر ابزاری که با api.registerTool(...) ثبت میشود باید در manifest
Plugin نیز اعلام شود:
{ "contracts": { "tools": ["my_tool", "workflow_tool"] }, "toolMetadata": { "workflow_tool": { "optional": true } }}OpenClaw توصیفگر اعتبارسنجیشده را از ابزار ثبتشده ثبت و cache میکند،
بنابراین Pluginها دادههای description یا schema را در manifest تکرار نمیکنند. قرارداد
manifest فقط مالکیت و کشف را اعلام میکند؛ اجرا همچنان پیادهسازی زندهٔ ابزار ثبتشده را
فراخوانی میکند.
برای ابزارهایی که با
api.registerTool(..., { optional: true }) ثبت شدهاند، مقدار
toolMetadata.<tool>.optional: true را تنظیم کنید تا OpenClaw بتواند تا زمانی که ابزار
بهصراحت allowlist نشده است، از بارگذاری زمان اجرای آن Plugin خودداری کند.
کاربران ابزارهای اختیاری را در پیکربندی فعال میکنند:
{ tools: { allow: ["workflow_tool"] },}- نام ابزارها نباید با ابزارهای core تداخل داشته باشد (تداخلها نادیده گرفته میشوند)
- ابزارهایی با اشیای ثبتنام بدشکل، از جمله نبود
parameters، بهجای شکستن اجرای agent، نادیده گرفته میشوند و در diagnostics مربوط به Plugin گزارش میشوند - برای ابزارهایی که اثر جانبی یا نیازمندی binary اضافی دارند، از
optional: trueاستفاده کنید - کاربران میتوانند با افزودن شناسهٔ Plugin به
tools.allowهمهٔ ابزارهای یک Plugin را فعال کنند
ثبت فرمانهای CLI
Pluginها میتوانند با api.registerCli گروههای فرمان ریشهٔ openclaw اضافه کنند. برای
هر ریشهٔ فرمان سطح بالا، descriptors ارائه کنید تا OpenClaw بتواند بدون بارگذاری مشتاقانهٔ
زمان اجرای هر Plugin، فرمان را نمایش دهد و مسیریابی کند.
register(api) { api.registerCli( ({ program }) => { const demo = program .command("demo-plugin") .description("Run demo plugin commands"); demo .command("ping") .description("Check that the plugin CLI is executable") .action(() => { console.log("demo-plugin:pong"); }); }, { descriptors: [ { name: "demo-plugin", description: "Run demo plugin commands", hasSubcommands: true, }, ], }, );}پس از نصب، ثبت زمان اجرا را بررسی و فرمان را اجرا کنید:
openclaw plugins inspect demo-plugin --runtime --jsonopenclaw demo-plugin pingقراردادهای import
همیشه از مسیرهای متمرکز openclaw/plugin-sdk/<subpath> import کنید:
// Wrong: monolithic root (deprecated, will be removed) برای مرجع کامل subpath، نمای کلی SDK را ببینید.
درون Plugin خود، برای importهای داخلی از فایلهای barrel محلی (api.ts، runtime-api.ts) استفاده کنید -
هرگز Plugin خودتان را از طریق مسیر SDK آن import نکنید.
برای Pluginهای ارائهدهنده، helperهای مخصوص ارائهدهنده را در همان barrelهای ریشهٔ package نگه دارید، مگر اینکه seam واقعاً عمومی باشد. نمونههای bundled فعلی:
- Anthropic: wrapperهای stream Claude و helperهای
service_tier/ beta - OpenAI: builderهای ارائهدهنده، helperهای مدل پیشفرض، ارائهدهندههای realtime
- OpenRouter: builder ارائهدهنده بههمراه helperهای onboarding/config
اگر یک helper فقط داخل یک package ارائهدهندهٔ bundled مفید است، آن را بهجای ارتقا به
openclaw/plugin-sdk/* روی همان seam ریشهٔ package نگه دارید.
برخی seamهای helper تولیدشدهٔ openclaw/plugin-sdk/<bundled-id> هنوز برای
نگهداری bundled-Pluginها، وقتی استفادهٔ owner رهگیریشده دارند، وجود دارند. با آنها بهعنوان
سطوح رزروشده رفتار کنید، نه الگوی پیشفرض برای Pluginهای جدید شخص ثالث.
چکلیست پیش از ارسال
OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s
package.json فرادادهٔ درست openclaw را دارد
OPENCLAW_DOCS_MARKER:calloutClose:
OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s manifest openclaw.plugin.json موجود و معتبر است OPENCLAW_DOCS_MARKER:calloutClose:
OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s
نقطهٔ ورود از defineChannelPluginEntry یا definePluginEntry استفاده میکند
OPENCLAW_DOCS_MARKER:calloutClose:
OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s
همهٔ importها از مسیرهای متمرکز plugin-sdk/<subpath> استفاده میکنند
OPENCLAW_DOCS_MARKER:calloutClose: