| name | plugin-builder |
| description | Scaffold complete elizaOS plugins with actions, providers, evaluators, and services. Triggers when user asks to "create plugin", "build elizaOS extension", or "scaffold plugin structure" |
| allowed-tools | Write, Read, Edit, Grep, Glob, Bash |
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
```bash
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