Skip to main content

Overview

Plugin schemas define the structure and validation rules for plugin components, including actions, providers, services, and routes.

ServiceClass Interface

Defines the structure for service classes that can be registered by plugins.
interface ServiceClass {
  serviceType: string;                                    // Service identifier
  start(runtime: IAgentRuntime): Promise<Service>;       // Factory method
  stopRuntime?(runtime: IAgentRuntime): Promise<void>;   // Optional cleanup
  registerSendHandlers?(runtime: IAgentRuntime, service: Service): void;
  new (runtime?: IAgentRuntime): Service;                // Constructor
}

Example Service

import { Service, IAgentRuntime } from "@elizaos/core";

class TwitterService extends Service {
  static serviceType = "twitter";
  private client: TwitterClient;
  
  constructor(runtime?: IAgentRuntime) {
    super();
  }
  
  static async start(runtime: IAgentRuntime): Promise<Service> {
    const service = new TwitterService(runtime);
    await service.initialize(runtime);
    return service;
  }
  
  async initialize(runtime: IAgentRuntime): Promise<void> {
    const apiKey = runtime.getSetting("TWITTER_API_KEY");
    this.client = new TwitterClient(apiKey);
    runtime.logger.success("Twitter service started");
  }
  
  async stop(): Promise<void> {
    await this.client.disconnect();
  }
  
  static async stopRuntime(runtime: IAgentRuntime): Promise<void> {
    // Runtime-wide cleanup
  }
}

Route Schemas

Route Interface

Defines HTTP routes exposed by plugins.
type Route = PublicRoute | PrivateRoute

interface BaseRoute {
  type: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "STATIC";
  path: string;
  filePath?: string;  // For STATIC routes
  handler?: (req: RouteRequest, res: RouteResponse, runtime: IAgentRuntime) => Promise<void>;
  isMultipart?: boolean;  // For file uploads
  x402?: X402Config;      // Payment configuration
}

interface PublicRoute extends BaseRoute {
  public: true;
  name: string;  // Required for public routes
}

interface PrivateRoute extends BaseRoute {
  public?: false;
  name?: string;  // Optional for private routes
}

Request/Response Types

interface RouteRequest {
  body?: Record<string, RouteBodyValue>;
  params?: Record<string, string>;
  query?: Record<string, string | string[]>;
  headers?: Record<string, string | string[] | undefined>;
  method?: string;
  path?: string;
  url?: string;
}

interface RouteResponse {
  status: (code: number) => RouteResponse;
  json: (data: unknown) => RouteResponse;
  send: (data: unknown) => RouteResponse;
  end: () => RouteResponse;
  setHeader?: (name: string, value: string | string[]) => RouteResponse;
  sendFile?: (path: string) => RouteResponse;
  headersSent?: boolean;
}

Payment Configuration (x402)

interface X402Config {
  price: string;        // USDC base units (6 decimals, "1000000" = $1.00)
  network?: string;     // Override network
  payTo?: string;       // Override payment address
  description?: string; // Payment description
}

Route Examples

// Public API route
{
  type: "GET",
  path: "/api/status",
  public: true,
  name: "status",
  handler: async (req, res, runtime) => {
    res.status(200).json({
      status: "ok",
      version: "1.0.0"
    });
  }
}

// Private webhook route
{
  type: "POST",
  path: "/webhooks/twitter",
  public: false,
  handler: async (req, res, runtime) => {
    const event = req.body;
    // Process webhook
    res.status(200).json({ received: true });
  }
}

// Multipart file upload
{
  type: "POST",
  path: "/api/upload",
  public: true,
  name: "upload",
  isMultipart: true,
  handler: async (req, res, runtime) => {
    const file = req.body?.file;
    // Process file upload
    res.status(200).json({ uploaded: true });
  }
}

// Paid route with x402
{
  type: "POST",
  path: "/api/premium/analysis",
  public: true,
  name: "premium-analysis",
  x402: {
    price: "1000000",  // $1.00 USDC
    description: "Premium analysis service"
  },
  handler: async (req, res, runtime) => {
    // Payment verified by framework
    const result = await performAnalysis(req.body);
    res.status(200).json(result);
  }
}

// Static file serving
{
  type: "STATIC",
  path: "/public",
  filePath: "./public",
  public: true,
  name: "static-files"
}

Component Type Definitions

ComponentTypeDefinition

Defines entity component types with JSON schema validation.
interface ComponentTypeDefinition {
  name: string;
  description: string;
  schema: JSONSchemaDefinition;
  validator?: (data: Record<string, RouteBodyValue>) => boolean;
}

interface JSONSchemaDefinition {
  type?: string | string[];
  properties?: Record<string, JSONSchemaDefinition>;
  required?: string[];
  items?: JSONSchemaDefinition;
  [key: string]: JsonValue | undefined;
}

Example Component Type

const twitterProfileComponent: ComponentTypeDefinition = {
  name: "TwitterProfile",
  description: "Twitter profile information",
  schema: {
    type: "object",
    properties: {
      username: { type: "string" },
      followers: { type: "number" },
      verified: { type: "boolean" },
      bio: { type: "string" }
    },
    required: ["username"]
  },
  validator: (data) => {
    return typeof data.username === "string" && data.username.length > 0;
  }
};

Event Schemas

PluginEvents

Event handlers keyed by event type.
type PluginEvents = {
  [K in keyof EventPayloadMap]?: EventHandler<K>[];
}

type EventHandler<K extends keyof EventPayloadMap> = (
  payload: EventPayloadMap[K]
) => Promise<void>

Common Event Types

enum EventType {
  MESSAGE_RECEIVED = "message_received",
  MESSAGE_SENT = "message_sent",
  ACTION_STARTED = "action_started",
  ACTION_COMPLETED = "action_completed",
  WORLD_JOINED = "world_joined",
  WORLD_CONNECTED = "world_connected",
  ENTITY_JOINED = "entity_joined",
  ENTITY_LEFT = "entity_left",
  REACTION_RECEIVED = "reaction_received",
  POST_GENERATED = "post_generated",
  EVALUATOR_STARTED = "evaluator_started",
  EVALUATOR_COMPLETED = "evaluator_completed",
  RUN_STARTED = "run_started",
  RUN_ENDED = "run_ended",
  RUN_TIMEOUT = "run_timeout",
  CONTROL_MESSAGE = "control_message"
}

Event Payload Examples

interface MessagePayload {
  runtime: IAgentRuntime;
  message: Memory;
}

interface ActionEventPayload {
  runtime: IAgentRuntime;
  roomId: UUID;
  messageId: UUID;
  content: Content;
  world?: World;
}

interface WorldPayload {
  runtime: IAgentRuntime;
  world: World;
  rooms: Room[];
  entities: Entity[];
  source?: string;
  onComplete?: () => void;
}

interface EntityPayload {
  runtime: IAgentRuntime;
  entityId: UUID;
  worldId?: UUID;
  roomId?: string;
  source?: string;
  metadata?: Record<string, unknown>;
}

Model Handler Schemas

ModelHandler Interface

interface ModelHandler<TParams, TResult> {
  handler: (runtime: IAgentRuntime, params: TParams) => Promise<TResult>;
  provider: string;           // Plugin name
  priority?: number;          // Selection priority (higher = preferred)
  registrationOrder?: number; // Tie-breaker
  maxInputTokens?: number;    // Input token limit
}

Model Type Mapping

interface ModelParamsMap {
  TEXT_SMALL: GenerateTextParams;
  TEXT_LARGE: GenerateTextParams;
  TEXT_EMBEDDING: TextEmbeddingParams | string | null;
  TEXT_TOKENIZER_ENCODE: TokenizeTextParams;
  TEXT_TOKENIZER_DECODE: DetokenizeTextParams;
  TEXT_REASONING_SMALL: GenerateTextParams;
  TEXT_REASONING_LARGE: GenerateTextParams;
  IMAGE: ImageGenerationParams;
  IMAGE_DESCRIPTION: ImageDescriptionParams | string;
  TRANSCRIPTION: TranscriptionParams | Buffer | string;
  TEXT_TO_SPEECH: TextToSpeechParams | string;
  AUDIO: AudioProcessingParams;
  VIDEO: VideoProcessingParams;
  OBJECT_SMALL: ObjectGenerationParams;
  OBJECT_LARGE: ObjectGenerationParams;
  TEXT_COMPLETION: GenerateTextParams;
  RESEARCH: ResearchParams;
  SAFEGUARD: SafeguardParams;
}

interface ModelResultMap {
  TEXT_SMALL: string;
  TEXT_LARGE: string;
  TEXT_EMBEDDING: number[];
  TEXT_TOKENIZER_ENCODE: number[];
  TEXT_TOKENIZER_DECODE: string;
  TEXT_REASONING_SMALL: string;
  TEXT_REASONING_LARGE: string;
  IMAGE: ImageGenerationResult[];
  IMAGE_DESCRIPTION: ImageDescriptionResult;
  TRANSCRIPTION: string;
  TEXT_TO_SPEECH: Buffer | ArrayBuffer | Uint8Array;
  AUDIO: Buffer | ArrayBuffer | Uint8Array | Record<string, JsonValue>;
  VIDEO: Buffer | ArrayBuffer | Uint8Array | Record<string, JsonValue>;
  OBJECT_SMALL: Record<string, JsonValue>;
  OBJECT_LARGE: Record<string, JsonValue>;
  TEXT_COMPLETION: string;
  RESEARCH: ResearchResult;
  SAFEGUARD: SafeguardResult;
}

Streaming Model Result

interface TextStreamResult {
  textStream: AsyncIterable<string>;
  text: Promise<string>;
  usage: Promise<TokenUsage | undefined>;
  finishReason: Promise<string | undefined>;
}

type PluginModelResult<K extends keyof ModelResultMap> =
  K extends StreamableModelType
    ? ModelResultMap[K] | TextStreamResult
    : ModelResultMap[K]

Validation Schemas

Plugin Validation

function validatePlugin(plugin: unknown): {
  isValid: boolean;
  errors: string[];
}

function isValidPluginShape(obj: unknown): obj is Plugin

Validation Example

import { validatePlugin } from "@elizaos/core";

const plugin = {
  name: "my-plugin",
  description: "My custom plugin",
  actions: [/* ... */]
};

const validation = validatePlugin(plugin);
if (!validation.isValid) {
  console.error("Plugin validation failed:", validation.errors);
  // Errors: ["Plugin actions must be an array of action objects"]
}

Bootstrap Plugin Example

The bootstrap plugin demonstrates complete plugin structure:
export function createBootstrapPlugin(config: CapabilityConfig = {}): Plugin {
  return {
    name: "bootstrap",
    description: "Agent bootstrap with basic actions and evaluators",
    
    actions: [
      replyAction,
      ignoreAction,
      noneAction,
      ...(config.advancedCapabilities ? advancedActions : [])
    ],
    
    providers: [
      actionsProvider,
      characterProvider,
      timeProvider,
      worldProvider,
      ...(config.advancedCapabilities ? advancedProviders : [])
    ],
    
    evaluators: [
      ...(config.disableBasic ? [] : basicEvaluators)
    ],
    
    services: [
      AgentEventService,
      TaskService,
      EmbeddingGenerationService,
      ...(config.advancedCapabilities ? advancedServices : [])
    ],
    
    events: {
      [EventType.MESSAGE_SENT]: [
        async (payload: MessagePayload) => {
          payload.runtime.logger.debug("Message sent");
        }
      ],
      [EventType.WORLD_JOINED]: [
        async (payload: WorldPayload) => {
          await handleServerSync(payload);
        }
      ]
    },
    
    routes: [
      ...(config.enableAutonomy ? autonomyRoutes : [])
    ]
  };
}

Type Safety

TypeScript provides full type safety for plugin schemas:
// Type-safe model handler
const plugin: Plugin = {
  name: "custom-llm",
  description: "Custom LLM provider",
  
  models: {
    TEXT_SMALL: async (runtime, params) => {
      // params is typed as GenerateTextParams
      const prompt = params.prompt;
      // return must be string or TextStreamResult
      return "Generated text";
    },
    
    IMAGE_DESCRIPTION: async (runtime, params) => {
      // params is typed as ImageDescriptionParams | string
      const imageUrl = typeof params === "string" ? params : params.imageUrl;
      // return must be ImageDescriptionResult
      return {
        description: "An image",
        title: "Image"
      };
    }
  }
};

See Also

Build docs developers (and LLMs) love