Real-World Plugin and Project Patterns in Eliza

This guide documents the actual patterns and structures used in the Eliza framework based on examination of real plugin implementations and project structures.

Plugin Structure Patterns

Basic Plugin Structure

Every plugin follows this core structure (from plugin-starter):

import type { Plugin } from '@elizaos/core';

export const myPlugin: Plugin = {
  name: 'plugin-name',
  description: 'Plugin description',
  
  // Core components
  actions: [],      // Actions the plugin provides
  providers: [],    // Data providers
  services: [],     // Background services
  evaluators: [],   // Response evaluators
  
  // Optional components
  init: async (config) => {},  // Initialization logic
  models: {},       // Custom model implementations
  routes: [],       // HTTP routes
  events: {},       // Event handlers
  tests: [],        // Test suites
  dependencies: [], // Other required plugins
};

Real Examples

Bootstrap Plugin (plugin-bootstrap)

The most complex and comprehensive plugin that provides core functionality:

export const bootstrapPlugin: Plugin = {
  name: 'bootstrap',
  description: 'Agent bootstrap with basic actions and evaluators',
  actions: [
    actions.replyAction,
    actions.followRoomAction,
    actions.ignoreAction,
    actions.sendMessageAction,
    actions.generateImageAction,
    // ... more actions
  ],
  providers: [
    providers.timeProvider,
    providers.entitiesProvider,
    providers.characterProvider,
    providers.recentMessagesProvider,
    // ... more providers
  ],
  services: [TaskService],
  evaluators: [evaluators.reflectionEvaluator],
  events: {
    [EventType.MESSAGE_RECEIVED]: [messageReceivedHandler],
    [EventType.POST_GENERATED]: [postGeneratedHandler],
    // ... more event handlers
  }
};

Service Plugins (Discord, Telegram)

Platform integration plugins focus on service implementation:

// Discord Plugin
const discordPlugin: Plugin = {
  name: "discord",
  description: "Discord service plugin for integration with Discord servers",
  services: [DiscordService],
  actions: [
    chatWithAttachments,
    downloadMedia,
    joinVoice,
    leaveVoice,
    summarize,
    transcribeMedia,
  ],
  providers: [channelStateProvider, voiceStateProvider],
  tests: [new DiscordTestSuite()],
  init: async (config, runtime) => {
    // Check for required API tokens
    const token = runtime.getSetting("DISCORD_API_TOKEN");
    if (!token) {
      logger.warn("Discord API Token not provided");
    }
  },
};

// Telegram Plugin (minimal)
const telegramPlugin: Plugin = {
  name: TELEGRAM_SERVICE_NAME,
  description: 'Telegram client plugin',
  services: [TelegramService],
  tests: [new TelegramTestSuite()],
};

Action Patterns

Actions follow a consistent structure with validation and execution:

const helloWorldAction: Action = {
  name: 'HELLO_WORLD',
  similes: ['GREET', 'SAY_HELLO'],  // Alternative names
  description: 'Responds with a simple hello world message',
  
  validate: async (runtime, message, state) => {
    // Return true if action can be executed
    return true;
  },
  
  handler: async (runtime, message, state, options, callback, responses) => {
    try {
      const responseContent: Content = {
        text: 'hello world!',
        actions: ['HELLO_WORLD'],
        source: message.content.source,
      };
      
      if (callback) {
        await callback(responseContent);
      }
      
      return responseContent;
    } catch (error) {
      logger.error('Error in HELLO_WORLD action:', error);
      throw error;
    }
  },
  
  examples: [
    [
      {
        name: '{{name1}}',
        content: { text: 'Can you say hello?' }
      },
      {
        name: '{{name2}}',
        content: { 
          text: 'hello world!',
          actions: ['HELLO_WORLD']
        }
      }
    ]
  ]
};

Complex Action Example (Reply Action)

export const replyAction = {
  name: 'REPLY',
  similes: ['GREET', 'REPLY_TO_MESSAGE', 'SEND_REPLY', 'RESPOND'],
  description: 'Replies to the current conversation',
  
  validate: async (runtime) => true,
  
  handler: async (runtime, message, state, options, callback, responses) => {
    // Compose state with providers
    state = await runtime.composeState(message, ['RECENT_MESSAGES']);
    
    // Generate response using LLM
    const prompt = composePromptFromState({ state, template: replyTemplate });
    const response = await runtime.useModel(ModelType.OBJECT_LARGE, { prompt });
    
    const responseContent = {
      thought: response.thought,
      text: response.message || '',
      actions: ['REPLY'],
    };
    
    await callback(responseContent);
    return true;
  }
};

Provider Patterns

Providers supply contextual data to the agent:

export const timeProvider: Provider = {
  name: 'TIME',
  get: async (runtime, message) => {
    const currentDate = new Date();
    const options = {
      timeZone: 'UTC',
      dateStyle: 'full' as const,
      timeStyle: 'long' as const,
    };
    const humanReadable = new Intl.DateTimeFormat('en-US', options).format(currentDate);
    
    return {
      data: { time: currentDate },
      values: { time: humanReadable },
      text: `The current date and time is ${humanReadable}.`,
    };
  },
};

Service Patterns

Services run in the background and handle ongoing tasks:

export class TaskService extends Service {
  static serviceType = ServiceType.TASK;
  capabilityDescription = 'The agent is able to schedule and execute tasks';
  
  static async start(runtime: IAgentRuntime): Promise<Service> {
    const service = new TaskService(runtime);
    await service.startTimer();
    return service;
  }
  
  static async stop(runtime: IAgentRuntime) {
    const service = runtime.getService(ServiceType.TASK);
    if (service) {
      await service.stop();
    }
  }
  
  private async startTimer() {
    this.timer = setInterval(async () => {
      await this.checkTasks();
    }, this.TICK_INTERVAL);
  }
}

Platform Service Example (Discord)

export class DiscordService extends Service implements IDiscordService {
  static serviceType: string = DISCORD_SERVICE_NAME;
  capabilityDescription = "The agent is able to send and receive messages on discord";
  
  constructor(runtime: IAgentRuntime) {
    super(runtime);
    
    // Parse environment configuration
    const token = runtime.getSetting("DISCORD_API_TOKEN");
    if (!token) {
      this.client = null;
      return;
    }
    
    // Initialize Discord client
    this.client = new DiscordJsClient({
      intents: [/* Discord intents */],
      partials: [/* Discord partials */]
    });
    
    // Set up event handlers
    this.setupEventHandlers();
  }
}

Project Structure Patterns

Single Agent Project

// packages/project-starter/src/index.ts
import { type IAgentRuntime, type Project, type ProjectAgent } from '@elizaos/core';
import { character } from './character.ts';

const initCharacter = ({ runtime }: { runtime: IAgentRuntime }) => {
  logger.info('Initializing character');
  logger.info('Name: ', character.name);
};

export const projectAgent: ProjectAgent = {
  character,
  init: async (runtime: IAgentRuntime) => await initCharacter({ runtime }),
  // plugins: [starterPlugin], // Custom plugins here
};

const project: Project = {
  agents: [projectAgent],
};

export default project;

Character Configuration

Characters define personality and plugin configuration:

export const character: Character = {
  name: 'Eliza',
  plugins: [
    // Core plugins first
    '@elizaos/plugin-sql',
    
    // Conditional plugins based on environment
    ...(process.env.ANTHROPIC_API_KEY ? ['@elizaos/plugin-anthropic'] : []),
    ...(process.env.OPENAI_API_KEY ? ['@elizaos/plugin-openai'] : []),
    ...(process.env.DISCORD_API_TOKEN ? ['@elizaos/plugin-discord'] : []),
    ...(process.env.TELEGRAM_BOT_TOKEN ? ['@elizaos/plugin-telegram'] : []),
    
    // Bootstrap plugin (unless explicitly disabled)
    ...(!process.env.IGNORE_BOOTSTRAP ? ['@elizaos/plugin-bootstrap'] : []),
  ],
  settings: {
    secrets: {},
    avatar: 'https://elizaos.github.io/eliza-avatars/Eliza/portrait.png',
  },
  system: 'Respond to all messages in a helpful, conversational manner...',
  bio: [
    'Engages with all types of questions and conversations',
    'Provides helpful, concise responses',
    // ...
  ],
  topics: ['general knowledge', 'problem solving', 'technology'],
  messageExamples: [/* conversation examples */],
  style: {
    all: ['Keep responses concise', 'Use clear language'],
    chat: ['Be conversational', 'Show personality'],
  },
};

Plugin Registration and Initialization

Environment-Based Plugin Loading

Plugins are conditionally loaded based on environment variables:

const plugins = [
  // Always loaded
  '@elizaos/plugin-sql',
  
  // Conditionally loaded based on API keys
  ...(process.env.ANTHROPIC_API_KEY ? ['@elizaos/plugin-anthropic'] : []),
  ...(process.env.OPENAI_API_KEY ? ['@elizaos/plugin-openai'] : []),
  
  // Platform plugins
  ...(process.env.DISCORD_API_TOKEN ? ['@elizaos/plugin-discord'] : []),
  ...(process.env.TELEGRAM_BOT_TOKEN ? ['@elizaos/plugin-telegram'] : []),
];

Plugin Initialization

Plugins can have initialization logic:

const myPlugin: Plugin = {
  name: 'my-plugin',
  config: {
    EXAMPLE_VARIABLE: process.env.EXAMPLE_VARIABLE,
  },
  async init(config: Record<string, string>) {
    // Validate configuration
    const validatedConfig = await configSchema.parseAsync(config);
    
    // Set environment variables
    for (const [key, value] of Object.entries(validatedConfig)) {
      if (value) process.env[key] = value;
    }
  },
};

Event Handling Patterns

Plugins can handle various system events:

const events = {
  [EventType.MESSAGE_RECEIVED]: [
    async (payload: MessagePayload) => {
      await messageReceivedHandler({
        runtime: payload.runtime,
        message: payload.message,
        callback: payload.callback,
      });
    },
  ],
  
  [EventType.WORLD_JOINED]: [
    async (payload: WorldPayload) => {
      await handleServerSync(payload);
    },
  ],
  
  [EventType.ENTITY_JOINED]: [
    async (payload: EntityPayload) => {
      await syncSingleUser(/* params */);
    },
  ],
};

Multi-Agent Projects

While the examples show single-agent projects, the structure supports multiple agents:

const project: Project = {
  agents: [
    {
      character: elizaCharacter,
      init: async (runtime) => { /* Eliza init */ },
      plugins: [/* Eliza plugins */],
    },
    {
      character: assistantCharacter,
      init: async (runtime) => { /* Assistant init */ },
      plugins: [/* Assistant plugins */],
    },
  ],
};

Environment Variable Usage

Common environment variables used by plugins:

# AI Model Providers
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GOOGLE_GENERATIVE_AI_API_KEY=
OLLAMA_API_ENDPOINT=

# Platform Integrations
DISCORD_API_TOKEN=
TELEGRAM_BOT_TOKEN=
TWITTER_API_KEY=
TWITTER_API_SECRET_KEY=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=

# Plugin Control
IGNORE_BOOTSTRAP=  # Skip bootstrap plugin
CHANNEL_IDS=       # Restrict Discord to specific channels

# Database
POSTGRES_URL=
PGLITE_DATA_DIR=

Best Practices

  1. Plugin Dependencies: Use the dependencies array to specify required plugins
  2. Conditional Loading: Check environment variables before loading platform-specific plugins
  3. Service Initialization: Handle missing API tokens gracefully in service constructors
  4. Event Handlers: Keep event handlers focused and delegate to specialized functions
  5. Provider Data: Return structured data with data, values, and text fields
  6. Action Validation: Always implement validation logic before execution
  7. Error Handling: Use try-catch blocks and log errors appropriately
  8. Type Safety: Use TypeScript types from @elizaos/core for all plugin components