Skip to main content
Channel plugins integrate messaging platforms with OpenClaw. A channel plugin implements adapters for message handling, authentication, status checks, and more.

Channel Structure

A channel plugin implements the ChannelPlugin interface:
import type { ChannelPlugin } from "openclaw/plugin-sdk";

const myChannelPlugin: ChannelPlugin = {
  id: "my-channel",
  meta: {
    id: "my-channel",
    label: "My Channel",
    selectionLabel: "My Channel (plugin)",
    docsPath: "/channels/my-channel",
    docsLabel: "my-channel",
    blurb: "Custom messaging platform",
    order: 100
  },
  capabilities: {
    supportsDm: true,
    supportsGroup: true,
    supportsPolling: false
  },
  config: {
    /* Config adapter */
  },
  gateway: {
    /* Gateway adapter */
  },
  outbound: {
    /* Outbound adapter */
  },
  status: {
    /* Status adapter */
  }
};

Registering Channels

Register channels via the Plugin API:
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";

const plugin = {
  id: "my-channel",
  name: "My Channel",
  description: "Custom channel plugin",
  configSchema: emptyPluginConfigSchema(),
  register(api: OpenClawPluginApi) {
    api.registerChannel({ plugin: myChannelPlugin });
  }
};

export default plugin;

Core Adapters

Config Adapter

The config adapter manages channel configuration and accounts:
config: {
  resolveAccount: (config, accountId) => {
    // Return resolved account or null
    const section = config.channels?.myChannel;
    if (!section?.enabled) return null;
    
    return {
      accountId: accountId ?? "default",
      config,
      apiKey: section.apiKey,
      serverUrl: section.serverUrl
    };
  },
  
  listAccounts: (config) => {
    // Return list of account IDs
    const section = config.channels?.myChannel;
    return section?.enabled ? ["default"] : [];
  }
}

Gateway Adapter

The gateway adapter handles incoming messages:
gateway: {
  start: async ({ account, logger }) => {
    // Connect to messaging platform
    const client = new MyChannelClient(account.apiKey);
    
    client.on("message", async (message) => {
      // Process incoming message
      logger.info(`Received message: ${message.text}`);
      
      // Dispatch to agent
      await handleInboundMessage({
        channelId: "my-channel",
        from: message.senderId,
        to: message.recipientId,
        text: message.text,
        accountId: account.accountId
      });
    });
    
    await client.connect();
    
    return client;
  },
  
  stop: async (client) => {
    await client.disconnect();
  }
}

Outbound Adapter

The outbound adapter sends messages:
outbound: {
  send: async ({ account, to, payload }) => {
    const client = new MyChannelClient(account.apiKey);
    
    if (payload.kind === "text") {
      await client.sendMessage(to, payload.text);
      return { ok: true };
    }
    
    return { ok: false, error: "Unsupported payload kind" };
  }
}

Status Adapter

The status adapter provides connection status:
status: {
  describe: async ({ account }) => {
    const client = new MyChannelClient(account.apiKey);
    const isConnected = await client.ping();
    
    return {
      summary: isConnected ? "connected" : "disconnected",
      issues: isConnected ? [] : [
        { severity: "error", message: "Not connected" }
      ]
    };
  }
}

Advanced Adapters

Authentication Adapter

Handle login flows:
auth: {
  loginWithQr: {
    start: async ({ account }) => {
      // Generate QR code URL
      const { qrUrl, sessionId } = await generateQr();
      return { qrUrl, sessionId };
    },
    wait: async ({ sessionId }) => {
      // Poll for authentication
      const auth = await pollAuth(sessionId);
      return {
        ok: auth.success,
        creds: auth.credentials
      };
    }
  }
}

Pairing Adapter

Manage device pairing:
pairing: {
  idLabel: "userId",
  normalizeAllowEntry: (entry) => entry.toLowerCase(),
  
  list: async ({ account }) => {
    // Return pending pairing requests
    const requests = await fetchPairingRequests(account);
    return requests.map(r => ({
      id: r.id,
      sender: r.userId,
      timestamp: r.timestamp
    }));
  },
  
  approve: async ({ account, id }) => {
    await approvePairing(account, id);
    return { ok: true };
  },
  
  reject: async ({ account, id }) => {
    await rejectPairing(account, id);
    return { ok: true };
  }
}

Groups Adapter

Manage group chats:
groups: {
  leave: async ({ account, groupId }) => {
    await leaveGroup(account, groupId);
    return { ok: true };
  },
  
  listMembers: async ({ account, groupId }) => {
    const members = await fetchGroupMembers(account, groupId);
    return members.map(m => m.userId);
  }
}

Mentions Adapter

Handle @mentions in groups:
mentions: {
  buildMentionText: ({ botName, mentionMode }) => {
    if (mentionMode === "at") return `@${botName}`;
    return botName;
  },
  
  normalizeText: (text) => {
    // Strip mention markers
    return text.replace(/@[\w]+/g, "").trim();
  }
}

Directory Adapter

List users and groups:
directory: {
  listPeers: async ({ account }) => {
    const users = await fetchUsers(account);
    return users.map(u => ({
      kind: "peer" as const,
      id: u.id,
      name: u.displayName
    }));
  },
  
  listGroups: async ({ account }) => {
    const groups = await fetchGroups(account);
    return groups.map(g => ({
      kind: "group" as const,
      id: g.id,
      name: g.name
    }));
  }
}

Example: Matrix Channel Plugin

The Matrix channel plugin demonstrates a complete implementation:
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) {
    setMatrixRuntime(api.runtime);
    api.registerChannel({ plugin: matrixPlugin });
  }
};

export default plugin;
See the full source for details.

Message Handling

Channel plugins handle inbound messages by dispatching to the routing system:
import { recordInboundSession } from "openclaw/plugin-sdk";

gateway: {
  start: async ({ account, config, logger }) => {
    client.on("message", async (msg) => {
      // Record session
      const sessionKey = recordInboundSession({
        config,
        channelId: "my-channel",
        accountId: account.accountId,
        from: msg.senderId,
        to: msg.recipientId
      });
      
      // Dispatch to agent
      await dispatchToAgent({
        sessionKey,
        text: msg.text,
        // ...
      });
    });
  }
}

Outbound Message Types

Handle different payload types:
outbound: {
  send: async ({ account, to, payload }) => {
    if (payload.kind === "text") {
      await sendText(account, to, payload.text);
      return { ok: true };
    }
    
    if (payload.kind === "image") {
      await sendImage(account, to, payload.imagePath, payload.caption);
      return { ok: true };
    }
    
    if (payload.kind === "file") {
      await sendFile(account, to, payload.filePath, payload.caption);
      return { ok: true };
    }
    
    return { ok: false, error: `Unsupported payload kind: ${payload.kind}` };
  }
}

Configuration Schema

Define configuration with Zod:
import { z } from "zod";
import { buildChannelConfigSchema } from "openclaw/plugin-sdk";

const MyChannelConfigSchema = z.object({
  enabled: z.boolean().default(false),
  apiKey: z.string(),
  serverUrl: z.string().url(),
  accounts: z.array(
    z.object({
      id: z.string(),
      apiKey: z.string()
    })
  ).optional()
});

const configSchema = buildChannelConfigSchema(MyChannelConfigSchema, {
  apiKey: { sensitive: true, label: "API Key" },
  serverUrl: { label: "Server URL", placeholder: "https://api.example.com" }
});

Runtime Services

Channel plugins can use runtime services:
import { setMyChannelRuntime } from "./runtime.js";

const plugin = {
  register(api: OpenClawPluginApi) {
    // Store runtime for use in channel adapter
    setMyChannelRuntime(api.runtime);
    
    api.registerChannel({ plugin: myChannelPlugin });
  }
};
Then use in channel code:
import { getMyChannelRuntime } from "./runtime.js";

const runtime = getMyChannelRuntime();
const media = await runtime.media.loadWebMedia(url);
const logger = runtime.logging.getChildLogger({ channel: "my-channel" });

Best Practices

1. Error Handling

Handle connection errors gracefully:
gateway: {
  start: async ({ account, logger }) => {
    try {
      const client = await connect(account);
      return client;
    } catch (err) {
      logger.error(`Connection failed: ${err}`);
      throw err;
    }
  }
}

2. Reconnection Logic

Implement automatic reconnection:
gateway: {
  start: async ({ account, logger }) => {
    const client = new Client();
    
    client.on("disconnect", async () => {
      logger.warn("Disconnected, reconnecting...");
      await client.reconnect();
    });
    
    await client.connect();
    return client;
  }
}

3. Rate Limiting

Respect platform rate limits:
import { sleep } from "openclaw/plugin-sdk";

outbound: {
  send: async ({ account, to, payload }) => {
    await rateLimiter.acquire();
    await sendMessage(to, payload.text);
    await sleep(100); // Throttle
    return { ok: true };
  }
}

4. Status Checks

Provide detailed status information:
status: {
  describe: async ({ account }) => {
    const issues = [];
    
    if (!account.apiKey) {
      issues.push({ severity: "error", message: "Missing API key" });
    }
    
    const connected = await checkConnection(account);
    if (!connected) {
      issues.push({ severity: "error", message: "Not connected" });
    }
    
    return {
      summary: issues.length === 0 ? "connected" : "error",
      issues
    };
  }
}

Example Channels

Explore these built-in channel plugins:
  • Matrix - extensions/matrix - Matrix protocol integration
  • Microsoft Teams - extensions/msteams - Bot Framework integration
  • IRC - extensions/irc - IRC protocol
  • Mattermost - extensions/mattermost - Mattermost API
  • Nextcloud Talk - extensions/nextcloud-talk - Nextcloud integration
See the extensions directory for complete implementations.

Next Steps

Build docs developers (and LLMs) love