Plugin Builder Skill
A comprehensive skill that scaffolds production-ready elizaOS plugins with proper structure, TypeScript configuration, testing setup, and documentation.
When to Use
This skill activates when you need to:
-
Create a new elizaOS plugin from scratch
-
Add custom actions, providers, or services
-
Extend elizaOS with new capabilities
-
Build reusable plugin packages
Trigger phrases:
-
"Create a plugin for [capability]"
-
"Build an elizaOS plugin that [does something]"
-
"Scaffold a new plugin"
-
"Generate plugin structure for [feature]"
Capabilities
This skill can:
-
🏗️ Scaffold complete plugin structure
-
⚡ Generate actions with validation and handlers
-
📊 Create providers for context enrichment
-
✅ Build evaluators for response quality
-
🔌 Implement services for platform integrations
-
🧪 Set up comprehensive testing
-
📦 Configure TypeScript and build tools
-
📚 Generate complete documentation
Plugin Architecture
interface Plugin { name: string; // Unique plugin identifier description?: string; // Plugin purpose dependencies?: string[]; // Required plugins
// Extensibility Components actions?: Action[]; // Executable operations providers?: Provider[]; // Context enrichment evaluators?: Evaluator[]; // Response quality services?: typeof Service[]; // Platform integrations models?: ModelHandlers; // Custom model handlers
// Lifecycle Hooks init?(config: any, runtime: IAgentRuntime): Promise<void>; start?(runtime: IAgentRuntime): Promise<void>; stop?(runtime: IAgentRuntime): Promise<void>; }
Workflow
Phase 1: Requirements Analysis
Ask these questions:
Plugin Purpose: "What capability does this plugin add?"
-
Platform integration (Discord, Telegram, Slack)
-
External service (API, database, search)
-
Custom action (file operations, calculations)
-
Data enrichment (context, analytics)
-
Response enhancement (formatting, validation)
Components Needed:
-
Actions: User-triggered operations?
-
Providers: Context enrichment needed?
-
Evaluators: Response validation required?
-
Services: Long-running processes?
-
Models: Custom LLM handlers?
Dependencies:
-
External npm packages?
-
API credentials required?
-
Other elizaOS plugins?
-
System requirements?
Configuration:
-
Environment variables needed?
-
Runtime settings?
-
Secrets management?
Phase 2: Plugin Structure
plugin-{name}/ ├── package.json ├── tsconfig.json ├── .env.example ├── README.md ├── src/ │ ├── index.ts # Plugin export │ ├── types.ts # TypeScript interfaces │ ├── actions/ # Action implementations │ │ ├── index.ts │ │ └── {actionName}.ts │ ├── providers/ # Provider implementations │ │ ├── index.ts │ │ └── {providerName}.ts │ ├── evaluators/ # Evaluator implementations │ │ ├── index.ts │ │ └── {evaluatorName}.ts │ ├── services/ # Service implementations │ │ ├── index.ts │ │ └── {serviceName}.ts │ └── utils/ # Utility functions │ └── index.ts ├── tests/ # Tests │ ├── actions.test.ts │ ├── providers.test.ts │ ├── evaluators.test.ts │ └── integration.test.ts └── examples/ # Usage examples └── basic-usage.ts
Phase 3: Implementation
Step 1: Initialize Plugin
Create directory structure
mkdir -p plugin-{name}/{src/{actions,providers,evaluators,services,utils},tests,examples} cd plugin-{name}
Step 2: Package Configuration
{ "name": "@elizaos/plugin-{name}", "version": "1.0.0", "description": "{Plugin description}", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "vitest", "test:coverage": "vitest --coverage", "lint": "eslint src//*.ts", "format": "prettier --write "src//*.ts"" }, "dependencies": { "@elizaos/core": "latest", "zod": "^3.22.0" }, "devDependencies": { "@types/node": "^20.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.0.0", "prettier": "^3.0.0", "typescript": "^5.0.0", "vitest": "^1.0.0" }, "keywords": [ "elizaos", "plugin", "{keywords}" ], "author": "{Your Name}", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/{org}/plugin-{name}" } }
Step 3: TypeScript Configuration
{ "compilerOptions": { "target": "ES2022", "module": "ES2022", "lib": ["ES2022"], "moduleResolution": "node", "esModuleInterop": true, "declaration": true, "declarationMap": true, "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "tests"] }
Step 4: Create Types
// src/types.ts
import type { Action, Provider, Evaluator, Service, IAgentRuntime, Memory, State } from '@elizaos/core';
// Plugin Configuration export interface {PluginName}Config { apiKey?: string; baseUrl?: string; timeout?: number; // Add your config properties }
// Action Types export interface {Action}Input { // Input parameters }
export interface {Action}Output { // Output structure }
// Provider Types export interface {Provider}Data { // Provider data structure }
// Service Types export interface {Service}Config { // Service configuration }
// Export plugin type export interface {PluginName}Plugin { name: string; description: string; actions?: Action[]; providers?: Provider[]; evaluators?: Evaluator[]; services?: typeof Service[]; }
Step 5: Create Action
// src/actions/{actionName}.ts
import { type Action, type IAgentRuntime, type Memory, type State, type HandlerCallback } from '@elizaos/core'; import { z } from 'zod';
// Input validation schema const {action}Schema = z.object({ param1: z.string().min(1), param2: z.number().optional(), // Add your parameters });
export const {action}Action: Action = { name: '{ACTION_NAME}',
// Similar action names for triggering similes: [ 'SIMILAR_NAME_1', 'SIMILAR_NAME_2', 'ALTERNATIVE_NAME' ],
description: '{What this action does and when to use it}',
// Usage examples for training examples: [ [ { user: "{{user}}", content: { text: "Can you {action example}?" } }, { user: "{{agentName}}", content: { text: "{I'll {action} for you}", action: "{ACTION_NAME}" } } ], [ { user: "{{user}}", content: { text: "{Another way to trigger action}" } }, { user: "{{agentName}}", content: { text: "{Response}", action: "{ACTION_NAME}" } } ] ],
// Validate if action should execute validate: async ( runtime: IAgentRuntime, message: Memory, state?: State ): Promise<boolean> => { try { // Extract parameters from message const params = extractParams(message);
// Validate with Zod
{action}Schema.parse(params);
// Additional validation logic
if (!hasRequiredPermissions(runtime, message)) {
return false;
}
return true;
} catch (error) {
console.error('Validation failed:', error);
return false;
}
},
// Execute action handler: async ( runtime: IAgentRuntime, message: Memory, state?: State, options?: any, callback?: HandlerCallback ): Promise<string | null> => { try { // Extract and validate parameters const params = extractParams(message); const validated = {action}Schema.parse(params);
// Send progress update
if (callback) {
callback({
text: 'Processing {action}...',
action: '{ACTION_NAME}'
});
}
// Execute action logic
const result = await performAction(validated, runtime);
// Store result in memory if needed
await runtime.createMemory({
entityId: runtime.agentId,
roomId: message.roomId,
content: {
text: `Action {ACTION_NAME} completed`,
metadata: { result }
}
});
// Return success message
return formatSuccessResponse(result);
} catch (error) {
console.error('Action failed:', error);
// Return error message
return formatErrorResponse(error);
}
} };
// Helper functions function extractParams(message: Memory): any { // Extract parameters from message content return { param1: message.content.param1, param2: message.content.param2 }; }
async function performAction(params: any, runtime: IAgentRuntime): Promise<any> { // Implement action logic // Access runtime.getService() for services // Use runtime.createMemory() for storing data
return { success: true, data: {} }; }
function hasRequiredPermissions(runtime: IAgentRuntime, message: Memory): boolean { // Check permissions return true; }
function formatSuccessResponse(result: any): string {
return ✅ {Action} completed successfully: ${JSON.stringify(result)};
}
function formatErrorResponse(error: any): string {
return ❌ {Action} failed: ${error.message};
}
Step 6: Create Provider
// src/providers/{providerName}.ts
import { type Provider, type IAgentRuntime, type Memory, type State } from '@elizaos/core';
export const {provider}Provider: Provider = { name: '{PROVIDER_NAME}',
description: '{What context this provider adds}',
// Optional: only execute when explicitly requested dynamic: false,
// Optional: hide from public context private: false,
// Execution order (lower = earlier) position: 100,
// Gather and format context get: async ( runtime: IAgentRuntime, message: Memory, state?: State ): Promise<{ values: Record<string, any>; data: Record<string, any>; text: string; }> => { try { // Gather data const data = await gatherData(runtime, message);
// Format for template usage
const values = {
key1: data.value1,
key2: data.value2
};
// Format as text for LLM context
const text = formatAsText(data);
return {
values, // For template variables
data, // For programmatic access
text // For LLM context
};
} catch (error) {
console.error('Provider failed:', error);
// Return empty result on error
return {
values: {},
data: {},
text: ''
};
}
} };
async function gatherData(runtime: IAgentRuntime, message: Memory): Promise<any> { // Fetch data from external sources // Query database // Process information
return { value1: 'data', value2: 123 }; }
function formatAsText(data: any): string { // Format data as natural language for LLM return ` {Provider Name} Context:
- Value 1: ${data.value1}
- Value 2: ${data.value2} `.trim(); }
Step 7: Create Service
// src/services/{serviceName}.ts
import { Service, ServiceTypeName, type IAgentRuntime } from '@elizaos/core';
export class {Service}Service extends Service { static serviceType: ServiceTypeName = '{SERVICE_TYPE}' as ServiceTypeName;
capabilityDescription: string = '{What this service provides}';
private client: any; private config: any;
constructor(runtime: IAgentRuntime) { super(runtime);
// Initialize configuration
this.config = {
apiKey: runtime.character.settings.{service}ApiKey,
baseUrl: runtime.character.settings.{service}BaseUrl || 'https://api.example.com',
timeout: runtime.character.settings.{service}Timeout || 30000
};
}
// Start service static async start(runtime: IAgentRuntime): Promise<Service> { const service = new {Service}Service(runtime); await service.initialize(); return service; }
// Initialize service async initialize(): Promise<void> { try { // Initialize client this.client = createClient(this.config);
// Test connection
await this.client.healthCheck();
console.log('✅ {Service} initialized successfully');
} catch (error) {
console.error('❌ {Service} initialization failed:', error);
throw error;
}
}
// Stop service async stop(): Promise<void> { try { // Cleanup resources if (this.client) { await this.client.close(); }
console.log('✅ {Service} stopped successfully');
} catch (error) {
console.error('❌ {Service} stop failed:', error);
}
}
// Service methods async doSomething(params: any): Promise<any> { try { return await this.client.request(params); } catch (error) { console.error('Service request failed:', error); throw error; } } }
function createClient(config: any): any { // Create and return API client return { healthCheck: async () => true, request: async (params: any) => ({ success: true }), close: async () => {} }; }
Step 8: Create Plugin Index
// src/index.ts
import type { Plugin } from '@elizaos/core';
// Import components import { {action}Action } from './actions/{actionName}.js'; import { {provider}Provider } from './providers/{providerName}.js'; import { {Service}Service } from './services/{serviceName}.js';
// Export types export * from './types.js';
// Export plugin export const {plugin}Plugin: Plugin = { name: '@elizaos/plugin-{name}', description: '{Plugin description}',
// Optional: Dependencies dependencies: [],
// Components actions: [{action}Action], providers: [{provider}Provider], services: [{Service}Service],
// Lifecycle hooks async init(config: any, runtime: any): Promise<void> { console.log('Initializing {plugin} plugin...'); // Initialization logic },
async start(runtime: any): Promise<void> { console.log('Starting {plugin} plugin...'); // Startup logic },
async stop(runtime: any): Promise<void> { console.log('Stopping {plugin} plugin...'); // Cleanup logic } };
export default {plugin}Plugin;
Step 9: Create Tests
// tests/actions.test.ts
import { describe, it, expect, beforeEach } from 'vitest'; import { {action}Action } from '../src/actions/{actionName}'; import { createMockRuntime, createMockMessage } from '@elizaos/core/test';
describe('{Action} Action', () => { let runtime: any; let message: any;
beforeEach(() => { runtime = createMockRuntime(); message = createMockMessage({ content: { text: 'test message', param1: 'value1' } }); });
it('validates correct input', async () => { const isValid = await {action}Action.validate(runtime, message); expect(isValid).toBe(true); });
it('rejects invalid input', async () => { message.content.param1 = ''; const isValid = await {action}Action.validate(runtime, message); expect(isValid).toBe(false); });
it('executes successfully', async () => { const result = await {action}Action.handler(runtime, message); expect(result).toContain('success'); });
it('handles errors gracefully', async () => { // Force an error runtime.createMemory = () => { throw new Error('Test error'); };
const result = await {action}Action.handler(runtime, message);
expect(result).toContain('failed');
}); });
Step 10: Create Documentation
@elizaos/plugin-{name}
{Brief description of plugin purpose and capabilities}
Features
- ✨ {Feature 1}
- ⚡ {Feature 2}
- 🔒 {Feature 3}
Installation
npm install @elizaos/plugin-{name}
Configuration
Add the plugin to your character configuration:
import { {plugin}Plugin } from '@elizaos/plugin-{name}';
export const character: Character = {
// ... other config
plugins: [
'@elizaos/plugin-bootstrap',
{plugin}Plugin
],
settings: {
{service}ApiKey: process.env.{SERVICE}_API_KEY,
{service}BaseUrl: 'https://api.example.com',
{service}Timeout: 30000
}
};
Environment Variables
# Required
{SERVICE}_API_KEY=your-api-key
# Optional
{SERVICE}_BASE_URL=https://api.example.com
{SERVICE}_TIMEOUT=30000
Actions
{ACTION_NAME}
{Action description}
Usage:
User: Can you {action}?
Agent: {I'll do the action}
Parameters:
- param1
(string, required): {Description}
- param2
(number, optional): {Description}
Providers
{PROVIDER_NAME}
{Provider description}
Adds the following to agent context:
- {Context item 1}
- {Context item 2}
Services
{Service}Service
{Service description}
Methods:
- doSomething(params)
: {Method description}
Usage Examples
Basic Usage
import { AgentRuntime } from '@elizaos/core';
import { {plugin}Plugin } from '@elizaos/plugin-{name}';
import character from './character';
const runtime = new AgentRuntime({
character,
plugins: [{plugin}Plugin]
});
await runtime.initialize();
Using Actions Programmatically
import { {action}Action } from '@elizaos/plugin-{name}';
const result = await {action}Action.handler(
runtime,
message,
state,
options,
callback
);
Testing
# Run tests
npm test
# Run with coverage
npm run test:coverage
# Run in watch mode
npm run test:watch
Development
# Build
npm run build
# Watch mode
npm run dev
# Lint
npm run lint
# Format
npm run format
API Reference
{Detailed API documentation}
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT
Support
- 📚 Documentation
- 💬 Discord
- 🐛 Issues
## Plugin Templates
### 1. Platform Integration Plugin
For integrating new chat platforms (Slack, WhatsApp, etc.):
- Service for platform connection
- Actions for platform-specific features
- Message formatting providers
- Platform event handlers
### 2. External API Plugin
For accessing external services:
- Actions for API operations
- Providers for data enrichment
- Error handling and retries
- Rate limiting and caching
### 3. Data Processing Plugin
For data transformation and analysis:
- Actions for processing operations
- Providers for context enrichment
- Evaluators for result validation
- Caching for performance
### 4. Custom Model Plugin
For integrating custom LLMs:
- Model handlers for inference
- Token counting utilities
- Streaming support
- Fallback mechanisms
## Best Practices
1. **Type Safety**: Use TypeScript strictly, no `any` types
2. **Error Handling**: Catch and log all errors gracefully
3. **Validation**: Use Zod for input validation
4. **Testing**: Achieve >80% code coverage
5. **Documentation**: Document all public APIs
6. **Security**: Never log sensitive data
7. **Performance**: Implement caching where appropriate
8. **Compatibility**: Test with multiple elizaOS versions
9. **Dependencies**: Minimize external dependencies
10. **Versioning**: Follow semantic versioning
## Output Checklist
After generating a plugin:
✅ Directory structure created
✅ Package configuration
✅ TypeScript configuration
✅ Type definitions
✅ Actions implemented
✅ Providers implemented
✅ Services implemented
✅ Tests written (>80% coverage)
✅ Documentation complete
✅ Examples provided
✅ Environment template
✅ Build scripts configured
Then display:
🔌 Plugin "@elizaos/plugin-{name}" created successfully!
📋 Summary:
Name: @elizaos/plugin-{name}
Actions: {count}
Providers: {count}
Services: {count}
Dependencies: {list}
📂 Structure:
✅ src/index.ts - Plugin entry point
✅ src/actions/ - Action implementations
✅ src/providers/ - Provider implementations
✅ src/services/ - Service implementations
✅ tests/ - Test suite
✅ README.md - Documentation
🚀 Next steps:
- Install dependencies: npm install
- Build plugin: npm run build
- Run tests: npm test
- Test integration with character
- Publish to npm: npm publish
📖 Read README.md for usage instructions