Example 1: Simple Tool Plugin
The LLM Task plugin adds a single tool for running JSON-only LLM tasks.Structure
extensions/llm-task/
package.json
index.ts
README.md
src/
llm-task-tool.ts
Entry Point
// extensions/llm-task/index.ts
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
import { createLlmTaskTool } from "./src/llm-task-tool.js";
export default function register(api: OpenClawPluginApi) {
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, {
optional: true
});
}
Tool Implementation
// extensions/llm-task/src/llm-task-tool.ts
import type { OpenClawPluginApi, AnyAgentTool } from "openclaw/plugin-sdk";
import { Type } from "@sinclair/typebox";
import { stringEnum, optionalStringEnum } from "openclaw/plugin-sdk";
export function createLlmTaskTool(api: OpenClawPluginApi): AnyAgentTool {
return {
name: "llm-task",
description:
"Run a JSON-only LLM task (drafting, summarizing, classifying) with " +
"optional JSON Schema validation. Returns parsed JSON.",
input: Type.Object({
prompt: Type.String({
description: "Task prompt (what to generate/summarize/classify)"
}),
input: Type.Optional(
Type.Unknown({ description: "Input data for the task" })
),
schema: Type.Optional(
Type.Object({}, {
additionalProperties: true,
description: "JSON Schema for output validation"
})
),
provider: Type.Optional(Type.String({ description: "LLM provider" })),
model: Type.Optional(Type.String({ description: "Model name" })),
authProfileId: Type.Optional(Type.String()),
temperature: Type.Optional(Type.Number()),
maxTokens: Type.Optional(Type.Integer()),
timeoutMs: Type.Optional(Type.Integer())
}),
execute: async (args) => {
try {
// Build prompt
const fullPrompt = buildPrompt(args.prompt, args.input);
// Call LLM (simplified)
const response = await callLlm({
prompt: fullPrompt,
provider: args.provider,
model: args.model,
temperature: args.temperature,
maxTokens: args.maxTokens
});
// Parse JSON response
const json = JSON.parse(response.text);
// Validate against schema if provided
if (args.schema) {
validateJson(json, args.schema);
}
return {
result: "success",
details: { json }
};
} catch (err) {
api.logger.error(`LLM task failed: ${err}`);
return {
result: "error",
error: err instanceof Error ? err.message : String(err)
};
}
}
};
}
function buildPrompt(task: string, input?: unknown): string {
let prompt = task + "\n\n";
if (input) {
prompt += `Input:\n${JSON.stringify(input, null, 2)}\n\n`;
}
prompt += "Output only valid JSON (no code fences, no commentary).";
return prompt;
}
Configuration
{
"plugins": {
"entries": {
"llm-task": {
"enabled": true,
"config": {
"defaultProvider": "openai",
"defaultModel": "gpt-4",
"maxTokens": 800
}
}
}
},
"agents": {
"list": [
{ "id": "main", "tools": { "allow": ["llm-task"] } }
]
}
}
Example 2: Service Plugin
The Diagnostics (OpenTelemetry) plugin exports diagnostics events to OpenTelemetry.Entry Point
// extensions/diagnostics-otel/index.ts
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import { createDiagnosticsOtelService } from "./src/service.js";
const plugin = {
id: "diagnostics-otel",
name: "Diagnostics OpenTelemetry",
description: "Export diagnostics events to OpenTelemetry",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
api.registerService(createDiagnosticsOtelService());
}
};
export default plugin;
Service Implementation
// extensions/diagnostics-otel/src/service.ts
import type { OpenClawPluginService } from "openclaw/plugin-sdk";
import { onDiagnosticEvent } from "openclaw/plugin-sdk";
import { trace } from "@opentelemetry/api";
export function createDiagnosticsOtelService(): OpenClawPluginService {
return {
id: "diagnostics-otel",
start: async (ctx) => {
ctx.logger.info("Starting OpenTelemetry exporter");
// Subscribe to diagnostic events
onDiagnosticEvent((event) => {
const tracer = trace.getTracer("openclaw");
if (event.type === "session_state") {
const span = tracer.startSpan("session", {
attributes: {
"session.id": event.sessionId,
"session.state": event.state,
"session.messages": event.messageCount
}
});
span.end();
}
if (event.type === "usage") {
const span = tracer.startSpan("llm_call", {
attributes: {
"llm.provider": event.provider,
"llm.model": event.model,
"llm.input_tokens": event.inputTokens,
"llm.output_tokens": event.outputTokens
}
});
span.end();
}
});
ctx.logger.info("OpenTelemetry exporter started");
},
stop: async (ctx) => {
ctx.logger.info("Stopping OpenTelemetry exporter");
// Cleanup...
}
};
}
Configuration
{
"plugins": {
"entries": {
"diagnostics-otel": {
"enabled": true,
"config": {
"endpoint": "http://localhost:4318/v1/traces",
"serviceName": "openclaw-gateway"
}
}
}
}
}
Example 3: Channel Plugin
The Matrix channel plugin demonstrates a complete channel implementation.Entry Point
// extensions/matrix/index.ts
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import { matrixPlugin } from "./src/channel.js";
import { setMatrixRuntime } from "./src/runtime.js";
const plugin = {
id: "matrix",
name: "Matrix",
description: "Matrix channel plugin (matrix-js-sdk)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
// Store runtime for use in channel adapters
setMatrixRuntime(api.runtime);
api.registerChannel({ plugin: matrixPlugin });
}
};
export default plugin;
Channel Plugin
// extensions/matrix/src/channel.ts (simplified)
import type { ChannelPlugin } from "openclaw/plugin-sdk";
import {
buildChannelConfigSchema,
normalizeAccountId
} from "openclaw/plugin-sdk";
import { MatrixConfigSchema } from "./config-schema.js";
import { probeMatrix } from "./matrix/probe.js";
import { sendMessageMatrix } from "./matrix/send.js";
export const matrixPlugin: ChannelPlugin = {
id: "matrix",
meta: {
id: "matrix",
label: "Matrix",
selectionLabel: "Matrix (plugin)",
docsPath: "/channels/matrix",
docsLabel: "matrix",
blurb: "Open protocol for decentralized communication",
order: 70
},
capabilities: {
supportsDm: true,
supportsGroup: true,
supportsPolling: false,
supportsWebhook: false
},
configSchema: buildChannelConfigSchema(MatrixConfigSchema, {
homeserver: { label: "Homeserver URL", placeholder: "https://matrix.org" },
userId: { label: "User ID", placeholder: "@bot:matrix.org" },
accessToken: { sensitive: true, label: "Access Token" }
}),
config: {
resolveAccount: (config, accountId) => {
const section = config.channels?.matrix;
if (!section?.enabled) return null;
return {
accountId: normalizeAccountId(accountId),
config,
homeserver: section.homeserver,
userId: section.userId,
accessToken: section.accessToken
};
},
listAccounts: (config) => {
const section = config.channels?.matrix;
return section?.enabled ? ["default"] : [];
}
},
gateway: {
start: async ({ account, logger }) => {
logger.info(`Starting Matrix client for ${account.userId}`);
// Create Matrix client
const client = await createMatrixClient(account);
// Listen for messages
client.on("Room.timeline", async (event) => {
if (event.getType() !== "m.room.message") return;
const roomId = event.getRoomId();
const senderId = event.getSender();
const content = event.getContent();
// Dispatch to agent
await dispatchInboundMessage({
channelId: "matrix",
accountId: account.accountId,
from: senderId,
to: roomId,
text: content.body
});
});
await client.startClient();
return client;
},
stop: async (client) => {
await client.stopClient();
}
},
outbound: {
send: async ({ account, to, payload }) => {
if (payload.kind === "text") {
await sendMessageMatrix({
account,
roomId: to,
text: payload.text
});
return { ok: true };
}
return { ok: false, error: `Unsupported payload: ${payload.kind}` };
}
},
status: {
describe: async ({ account }) => {
const issues = [];
if (!account.accessToken) {
issues.push({
severity: "error" as const,
message: "Missing access token"
});
}
const probe = await probeMatrix(account);
if (!probe.ok) {
issues.push({
severity: "error" as const,
message: probe.error ?? "Connection failed"
});
}
return {
summary: issues.length === 0 ? "connected" : "error",
issues
};
}
}
};
Configuration
{
"channels": {
"matrix": {
"enabled": true,
"homeserver": "https://matrix.example.com",
"userId": "@bot:example.com",
"accessToken": "syt_...",
"dm": { "allow": ["@user:example.com"] },
"groups": {
"allow": ["!room:example.com"],
"requireMention": true
}
}
}
}
Example 4: Hook-Based Plugin
A plugin that logs session activity using hooks:import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { writeFile } from "node:fs/promises";
const plugin = {
id: "session-logger",
name: "Session Logger",
description: "Log session activity to disk",
register(api: OpenClawPluginApi) {
const sessions = new Map<string, SessionLog>();
// Session start
api.registerHook("session_start", async (event) => {
sessions.set(event.sessionId, {
startTime: Date.now(),
llmCalls: 0,
toolCalls: 0,
messages: 0
});
api.logger.info(`Session started: ${event.sessionId}`);
});
// LLM call
api.registerHook("llm_input", async (event) => {
const log = sessions.get(event.sessionId);
if (log) {
log.llmCalls++;
}
});
// Tool call
api.registerHook("after_tool_call", async (event, ctx) => {
const sessionId = ctx.sessionKey;
const log = sessionId && sessions.get(sessionId);
if (log) {
log.toolCalls++;
}
});
// Message sent
api.registerHook("message_sent", async (event) => {
// Increment message count...
});
// Session end
api.registerHook("session_end", async (event) => {
const log = sessions.get(event.sessionId);
if (!log) return;
const report = {
sessionId: event.sessionId,
duration: Date.now() - log.startTime,
messageCount: event.messageCount,
llmCalls: log.llmCalls,
toolCalls: log.toolCalls
};
const logPath = api.resolvePath(`logs/session-${event.sessionId}.json`);
await writeFile(logPath, JSON.stringify(report, null, 2));
sessions.delete(event.sessionId);
api.logger.info(`Session log written: ${logPath}`);
});
}
};
type SessionLog = {
startTime: number;
llmCalls: number;
toolCalls: number;
messages: number;
};
export default plugin;
Example 5: Multi-Tool Plugin
A plugin that registers multiple related tools:import type { OpenClawPluginApi, AnyAgentTool } from "openclaw/plugin-sdk";
import { Type } from "@sinclair/typebox";
import { stringEnum } from "openclaw/plugin-sdk";
function createGitTools(api: OpenClawPluginApi): AnyAgentTool[] {
return [
{
name: "git_status",
description: "Check git repository status",
input: Type.Object({
path: Type.String({ description: "Repository path" })
}),
execute: async (args) => {
const { stdout } = await execCommand("git status", { cwd: args.path });
return { result: "success", details: { output: stdout } };
}
},
{
name: "git_commit",
description: "Create a git commit",
input: Type.Object({
path: Type.String({ description: "Repository path" }),
message: Type.String({ description: "Commit message" }),
files: Type.Array(Type.String({ description: "Files to commit" }))
}),
execute: async (args) => {
await execCommand(`git add ${args.files.join(" ")}`, { cwd: args.path });
await execCommand(`git commit -m "${args.message}"`, { cwd: args.path });
return { result: "success" };
}
},
{
name: "git_log",
description: "View git commit history",
input: Type.Object({
path: Type.String({ description: "Repository path" }),
count: Type.Optional(Type.Integer({ description: "Number of commits", default: 10 }))
}),
execute: async (args) => {
const { stdout } = await execCommand(
`git log -${args.count ?? 10} --oneline`,
{ cwd: args.path }
);
return { result: "success", details: { log: stdout } };
}
}
];
}
const plugin = {
id: "git-tools",
name: "Git Tools",
description: "Git repository management tools",
register(api: OpenClawPluginApi) {
const tools = createGitTools(api);
for (const tool of tools) {
api.registerTool(tool, { optional: true });
}
api.logger.info(`Registered ${tools.length} git tools`);
}
};
export default plugin;
Exploring More Examples
Browse the extensions directory for 39+ complete plugin implementations:- Channels:
matrix,msteams,irc,mattermost,nextcloud-talk - Tools:
llm-task,lobster,voice-call,phone-control - Services:
diagnostics-otel,memory-lancedb,copilot-proxy - Auth:
google-antigravity-auth,minimax-portal-auth
Next Steps
- Plugin SDK Reference - Complete API documentation
- Publishing - Share your plugin on npm
- Built-in Extensions - Explore all built-in plugins

