Skip to content

Extension Loading

Extensions are OpenClaw's pluggable modules, including channel extensions (e.g., Microsoft Teams, Matrix) and feature extensions (e.g., LanceDB, OpenTelemetry). This chapter covers how they are discovered, loaded, and registered at runtime.

Loading Flow

Source: src/plugins/loader.ts

Step-by-Step

  1. Scan -- Traverse all subdirectories under extensions/
  2. Parse manifest -- Read each extension's package.json or dedicated manifest file
  3. Schema validation -- Ensure the manifest contains required fields (id, name, version, entry)
  4. Dynamic import -- Use import() to load the plugin's entry module
  5. Initialize -- Call the plugin's exported init function, passing in OpenClawPluginApi
  6. Register -- The plugin registers tools, channels, hooks, etc. through the API

Manifest Parsing

Every extension must have a manifest declaring plugin metadata and entry point:

typescript
// Plugin manifest (simplified)
interface PluginManifest {
  id: string;              // Unique identifier
  name: string;            // Display name
  version: string;         // Semver version
  entry: string;           // Entry module path
  kind: "extension";       // Plugin kind
  dependencies?: string[]; // Required dependencies
}

Dynamic Import

Source: src/plugins/loader.ts

Plugins are dynamically imported using the jiti resolver, which supports loading TypeScript source files directly:

typescript
// Dynamic import with jiti resolver (simplified)
// Supports loading:
// - Compiled JS from dist/
// - TypeScript source directly
// - ESM and CJS modules

TIP

jiti is a runtime TypeScript/ESM transformation tool that lets OpenClaw load TypeScript plugin source code directly, without requiring pre-compilation.

Extension Directory Structure

A typical extension structure:

extensions/msteams/
├── package.json           # Manifest + dependencies
├── src/
│   ├── index.ts          # Entry point (exports init function)
│   ├── plugin.ts         # ChannelPlugin implementation
│   ├── send.ts           # Message sending
│   ├── receive.ts        # Message receiving
│   └── auth.ts           # Authentication
└── tsconfig.json

Registry Recording

Source: src/plugins/registry.ts

Once loaded, each plugin is recorded as a PluginRecord:

typescript
interface PluginRecord {
  id: string;
  name: string;
  version: string;
  kind: "extension" | "skill" | "core";
  origin: string;          // Source path
  manifest: PluginManifest;
}

The registry manages all registrations by type:

Error Handling

Error handling strategy during the loading process:

Error TypeHandling
Missing manifestSkip, log warning
Schema validation failureSkip, log detailed error
Module import failureSkip, log error and stack trace
Init function throwsMark as failed, do not register

A single plugin's loading failure does not affect other plugins or Gateway startup.

Summary

  • Extensions are loaded through a manifest + dynamic import pattern
  • jiti enables direct loading of TypeScript source code
  • Loading flow: scan -> parse -> validate -> import -> initialize -> register
  • Error isolation -- a single plugin failure does not affect other plugins or system startup
  • All registrations are tracked through PluginRecord in the registry

Next: Hooks Mechanism

OpenClaw Source Code Tutorial