1. Project Overview & Quickstart (elizaOS/LICENSE)
LICENSE
Open-source repository elizaOS/LICENSE
Repository Details
- Repository: elizaOS/LICENSE
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (elizaOS/docs)
File: README.md
ElizaOS Documentation
Welcome to the official documentation repository for ElizaOS - a powerful framework for building AI agents with memory, planning, and tool use capabilities.
๐ About This Documentation
This repository contains the comprehensive documentation for ElizaOS, including:
- Getting Started - Quick setup guides and tutorials
- Core Concepts - Understanding agents, plugins, and the architecture
- Deep Dive - Advanced topics like memory systems, services, and event handling
- REST Reference - Complete REST API documentation for all modules
- Examples - Real-world implementations and patterns
Development
Prerequisites
- Node.js (v23 or higher)
- bun (or npm/yarn)
Local Development Setup
Install the Mintlify CLI to preview the documentation changes locally:
bun install -g mintRunning Locally
Navigate to the root of your documentation (where docs.json is located) and run:
mint devThis will start a local development server at http://localhost:3000 where you can preview your changes in real-time.
Project Structure
docs/
โโโ rest-reference/ # REST API documentation
โโโ core-concepts/ # Core concepts and architecture
โโโ deep-dive/ # Advanced topics
โโโ images/ # Documentation images
โโโ logo/ # Logo assets
โโโ snippets/ # Reusable code snippets
โโโ docs.json # Mintlify configuration
โโโ index.mdx # Landing page
โโโ quickstart.mdx # Quick start guide
โโโ development.mdx # Development guide๐ Contributing to Documentation
We welcome contributions to improve the ElizaOS documentation! Here's how you can help:
- Fork this repository
- Create a new branch for your changes
- Make your documentation improvements
- Test locally using
mint dev - Submit a pull request
Documentation Guidelines
- Use clear, concise language
- Include code examples where appropriate
- Follow the existing structure and formatting
- Test all code snippets
- Add images/diagrams for complex concepts
Publishing Changes
Changes are automatically deployed when merged to the main branch. The documentation is hosted using Mintlify's infrastructure.
Deployment Process
- Install the Mintlify GitHub App on your repository
- Push changes to your default branch
- Changes will be automatically deployed to production
Find the installation link in your Mintlify dashboard.
๐ง Troubleshooting
Common Issues
Dev environment not running
- Run
mint updateto ensure you have the latest version of the CLI - Check that you're in the correct directory with
docs.json
- Run
Page loads as 404
- Ensure you're running the command in a folder containing
docs.json - Check that your page is properly listed in the navigation
- Ensure you're running the command in a folder containing
Changes not reflecting
- Clear your browser cache
- Restart the development server
- Check for syntax errors in your MDX files
๐ Learn More
๐ License
This documentation is part of the ElizaOS project. Please refer to the main repository for license information.
Built with โค๏ธ using Mintlify
File: guides/action-planning.mdx
title: "Multi-Step Action Planning"
The Problem
Simple agents can do one thing at a time. But real tasks are complex:
- "Search the web, summarize results, and tweet the highlights"
- "Check my calendar, find a free slot, and schedule a meeting"
- "Analyze this data, generate a chart, and email it to the team"
Each of these requires multiple actions executed in sequence, with results from one feeding into the next.
How It Works
When the LLM returns multiple actions, ElizaOS creates an ActionPlan:
interface ActionPlan {
thought: string; // LLM's reasoning for this plan
totalSteps: number; // How many actions to execute
currentStep: number; // Which step we're on (0-indexed)
steps: ActionPlanStep[];
}
interface ActionPlanStep {
action: string; // Action name (e.g., "SEARCH", "TWEET")
status: 'pending' | 'completed' | 'failed';
error?: string; // If failed, why
result?: ActionResult; // Output from this step
}The plan flows through state, so each action can see what came before:
flowchart TB
User["User: Search for AI news and tweet the top story"]
LLM["LLM Decision: SEARCH, TWEETCreates plan with 2 steps"]
Step1["Step 1: SEARCHExecutes web searchStatus: completed"]
Step2["Step 2: TWEETReads previous resultsComposes tweetStatus: completed"]
User --> LLM
LLM --> Step1
Step1 -->|"result stored in actionResults"| Step2
style User fill:#e3f2fd
style LLM fill:#fff3e0
style Step1 fill:#e8f5e9
style Step2 fill:#e8f5e9Accessing Previous Results
In your action handler, access results from previous steps:
const myAction: Action = {
name: 'SUMMARIZE',
async handler(runtime, message, state, options, callback) {
// Get all previous action results
const previousResults = state?.data?.actionResults || [];
// Find specific action result
const searchResult = previousResults.find(r => r.action === 'SEARCH');
if (searchResult?.text) {
// Use search results to generate summary
const summary = await runtime.useModel(ModelType.TEXT_SMALL, {
prompt: `Summarize this: ${searchResult.text}`
});
return {
text: summary,
action: 'SUMMARIZE',
success: true
};
}
return {
text: 'No previous results to summarize',
action: 'SUMMARIZE',
success: false
};
}
};Accessing Plan State
The current action plan is available in state.data.actionPlan:
async handler(runtime, message, state, options, callback) {
const plan = state?.data?.actionPlan;
if (plan) {
console.log(`Step ${plan.currentStep + 1} of ${plan.totalSteps}`);
console.log(`LLM reasoning: ${plan.thought}`);
// Check previous steps
for (const step of plan.steps) {
if (step.status === 'completed') {
console.log(`${step.action}: ${step.result?.text}`);
} else if (step.status === 'failed') {
console.log(`${step.action} failed: ${step.error}`);
}
}
}
// Your action logic...
}Error Handling in Plans
When a step fails, the plan continues by default. Handle failures gracefully:
const resilientAction: Action = {
name: 'PROCESS_DATA',
async handler(runtime, message, state, options, callback) {
const previousResults = state?.data?.actionResults || [];
// Check if a required previous step failed
const fetchResult = previousResults.find(r => r.action === 'FETCH_DATA');
if (fetchResult?.success === false) {
// Previous step failed - use fallback
return {
text: 'Using cached data due to fetch failure',
action: 'PROCESS_DATA',
success: true,
data: { usedFallback: true }
};
}
// Normal processing
return {
text: `Processed: ${fetchResult?.text}`,
action: 'PROCESS_DATA',
success: true
};
}
};Retrieving Results After Execution
After message processing, retrieve action results programmatically:
// Process a message
const result = await runtime.handleMessage({
entityId: userId,
roomId: roomId,
content: { text: 'Search and summarize AI news', source: 'api' }
});
// Get the action results from that message
const actionResults = runtime.getActionResults(result.messageId);
for (const result of actionResults) {
console.log(`${result.action}: ${result.success ? 'OK' : 'FAILED'}`);
console.log(`Output: ${result.text}`);
if (result.data) {
console.log(`Data:`, result.data);
}
}Designing Actions for Chaining
When building actions that work well in chains:
Example: Data Pipeline
// Step 1: Fetch data
const fetchAction: Action = {
name: 'FETCH_DATA',
async handler(runtime, message, state) {
const data = await fetchFromAPI();
return {
text: `Fetched ${data.length} records`,
action: 'FETCH_DATA',
success: true,
data: { records: data } // Structured data for next step
};
}
};
// Step 2: Transform data (uses Step 1 output)
const transformAction: Action = {
name: 'TRANSFORM_DATA',
async handler(runtime, message, state) {
const fetchResult = state?.data?.actionResults?.find(
r => r.action === 'FETCH_DATA'
);
if (!fetchResult?.data?.records) {
return { text: 'No data to transform', action: 'TRANSFORM_DATA', success: false };
}
const transformed = fetchResult.data.records.map(r => ({
...r,
processed: true,
timestamp: Date.now()
}));
return {
text: `Transformed ${transformed.length} records`,
action: 'TRANSFORM_DATA',
success: true,
data: { records: transformed }
};
}
};
// Step 3: Store data (uses Step 2 output)
const storeAction: Action = {
name: 'STORE_DATA',
async handler(runtime, message, state) {
const transformResult = state?.data?.actionResults?.find(
r => r.action === 'TRANSFORM_DATA'
);
if (!transformResult?.data?.records) {
return { text: 'No data to store', action: 'STORE_DATA', success: false };
}
await database.insert(transformResult.data.records);
return {
text: `Stored ${transformResult.data.records.length} records`,
action: 'STORE_DATA',
success: true
};
}
};ActionResult Type
interface ActionResult {
action: string; // Action name
text: string; // Human-readable result
success: boolean; // Did it work?
error?: string; // Error message if failed
data?: Record<string, unknown>; // Structured data for chaining
}Next Steps
File: guides/add-multiple-agents.mdx
title: 'Add Multiple Agents'
Step 1: Add Hemingway
Add Hemingway to your project
You already have Shakespeare in src/character.ts from the previous guide. Now let's add another agent to our project so they can interact. We'll create a fresh character file for Hemingway using the CLI:
elizaos create --type agent hemingwayThis clones a JSON character template as hemingway.json. You'll now have:
src/character.ts- Shakespeare (TypeScript format)hemingway.json- Hemingway (JSON format)
Customize Hemingway's personality
Open hemingway.json and update it to customize Hemingway's personality:
{
"name": "hemingway",
"system": "Respond to all messages in a helpful, conversational manner. Provide assistance on a wide range of topics, using knowledge when needed. Be concise but thorough, friendly but professional. Use humor when appropriate and be empathetic to user needs. Provide valuable information and insights when questions are asked.", // [!code --]
"system": "You are Ernest Hemingway. Speak simply. Use short sentences. Cut the fat. Every word must earn its place. You respect Shakespeare but find him wordy. You've lived through war, love, and loss. Truth matters more than beauty. Experience matters more than theory. Never use two words when one will do. Avoid adjectives. Kill your darlings. The first draft of anything is shit, so make every word count now.", // [!code ++]
"bio": [
"hemingway is a helpful AI assistant created to provide assistance and engage in meaningful conversations.", // [!code --]
"hemingway is knowledgeable, creative, and always eager to help users with their questions and tasks." // [!code --]
"Ernest Hemingway, American novelist and journalist", // [!code ++]
"Master of the iceberg theory - show only what matters", // [!code ++]
"Champion of simple declarative sentences", // [!code ++]
"War correspondent who saw truth in trenches", // [!code ++]
"Believer that courage is grace under pressure", // [!code ++]
"Man who lived fully - bullfights, safaris, deep-sea fishing", // [!code ++]
"Writer who found truth in simple things", // [!code ++]
"Teacher who says: write one true sentence" // [!code ++]
]
}Just like in the previous guide, continue editing the other fields (topics, style, messageExamples, etc.) to match Hemingway as you see fit.
Step 2: Configure Discord and voice
Add Discord plugin to Hemingway
Add plugin-discord to Hemingway so he can join Shakespeare in our Discord server:
{
"plugins": [
"@elizaos/plugin-sql",
"@elizaos/plugin-openai",
"@elizaos/plugin-bootstrap",
"@elizaos/plugin-discord" // [!code ++]
]
}Configure agent-specific keys
But wait! We have our Discord environment variables defined in .env, but we need unique ones for each agent. Hemingway and Shakespeare need their own Discord bot tokens. How do we have agent-specific keys?
For that, we use secrets under settings in each character file. This allows each agent to have their own Discord bot identity:
For Hemingway (hemingway.json):
{
"settings": {
"secrets": {}, // [!code --]
"secrets": { // [!code ++]
"DISCORD_APPLICATION_ID": "YOUR_HEMINGWAY_APP_ID", // [!code ++]
"DISCORD_API_TOKEN": "YOUR_HEMINGWAY_BOT_TOKEN", // [!code ++]
}, // [!code ++]
"avatar": "https://example.com/hemingway-portrait.png"
}
}For Shakespeare (src/character.ts):
export const character: Character = {
settings: {
secrets: {}, // [!code --]
secrets: { // [!code ++]
DISCORD_APPLICATION_ID: "YOUR_SHAKESPEARE_APP_ID", // [!code ++]
DISCORD_API_TOKEN: "YOUR_SHAKESPEARE_BOT_TOKEN", // [!code ++]
}, // [!code ++]
avatar: 'https://example.com/shakespeare-portrait.png',
},
}Enable voice mode
Let's enable voice capabilities for our agents in Discord:
For Hemingway (hemingway.json):
{
"settings": {
"secrets": {
"DISCORD_APPLICATION_ID": "YOUR_HEMINGWAY_APP_ID",
"DISCORD_API_TOKEN": "YOUR_HEMINGWAY_BOT_TOKEN",
"DISCORD_VOICE_ENABLED": "true" // [!code ++]
}
}
}For Shakespeare (src/character.ts):
{
settings: {
secrets: {
DISCORD_APPLICATION_ID: "YOUR_SHAKESPEARE_APP_ID",
DISCORD_API_TOKEN: "YOUR_SHAKESPEARE_BOT_TOKEN",
DISCORD_VOICE_ENABLED: "true" // [!code ++]
}
}
}Add ElevenLabs voice provider
Now let's add plugin-elevenlabs to provide high-quality voice synthesis for our agents:
Add ElevenLabs plugin:
{
"plugins": [
"@elizaos/plugin-sql",
"@elizaos/plugin-openai",
"@elizaos/plugin-discord",
"@elizaos/plugin-bootstrap",
"@elizaos/plugin-elevenlabs" // [!code ++]
]
}export const character: Character = {
plugins: [
'@elizaos/plugin-sql',
'@elizaos/plugin-discord',
'@elizaos/plugin-elevenlabs', // [!code ++]
...(process.env.OPENAI_API_KEY?.trim() ? ['@elizaos/plugin-openai'] : []),
...(!process.env.IGNORE_BOOTSTRAP ? ['@elizaos/plugin-bootstrap'] : []),
],
}Configure voices for each agent
Now let's add the ElevenLabs secrets so each agent has their own distinct voice:
For Hemingway (hemingway.json):
{
"settings": {
"secrets": {
"DISCORD_APPLICATION_ID": "YOUR_HEMINGWAY_APP_ID",
"DISCORD_API_TOKEN": "YOUR_HEMINGWAY_BOT_TOKEN",
"DISCORD_VOICE_ENABLED": "true",
"ELEVENLABS_API_KEY": "your_elevenlabs_api_key", // [!code ++]
"ELEVENLABS_VOICE_ID": "Xb7hH8MSUJpSbSDYk0k2", // Deep male voice // [!code ++]
"ELEVENLABS_MODEL_ID": "eleven_multilingual_v2", // [!code ++]
"ELEVENLABS_VOICE_STABILITY": "0.5", // [!code ++]
"ELEVENLABS_OPTIMIZE_STREAMING_LATENCY": "0", // [!code ++]
"ELEVENLABS_OUTPUT_FORMAT": "pcm_16000", // [!code ++]
"ELEVENLABS_VOICE_SIMILARITY_BOOST": "0.75", // [!code ++]
"ELEVENLABS_VOICE_STYLE": "0", // [!code ++]
"ELEVENLABS_VOICE_USE_SPEAKER_BOOST": "true" // [!code ++]
}
}
}For Shakespeare (src/character.ts):
{
settings: {
secrets: {
DISCORD_APPLICATION_ID: "YOUR_SHAKESPEARE_APP_ID",
DISCORD_API_TOKEN: "YOUR_SHAKESPEARE_BOT_TOKEN",
DISCORD_VOICE_ENABLED: "true",
ELEVENLABS_API_KEY: "your_elevenlabs_api_key", // [!code ++]
ELEVENLABS_VOICE_ID: "21m00Tcm4TlvDq8ikWAM", // Theatrical British voice // [!code ++]
ELEVENLABS_MODEL_ID: "eleven_multilingual_v2", // [!code ++]
ELEVENLABS_VOICE_STABILITY: "0.3", // More variation for dramatic effect // [!code ++]
ELEVENLABS_OPTIMIZE_STREAMING_LATENCY: "0", // [!code ++]
ELEVENLABS_OUTPUT_FORMAT: "pcm_16000", // [!code ++]
ELEVENLABS_VOICE_SIMILARITY_BOOST: "0.75", // [!code ++]
ELEVENLABS_VOICE_STYLE: "0.5", // More expressive // [!code ++]
ELEVENLABS_VOICE_USE_SPEAKER_BOOST: "true" // [!code ++]
}
}
}Step 3: Configure multi-agent project
Add newly created agent to your project
Update your src/index.ts to include both agents so they start automatically:
import { logger, type IAgentRuntime, type Project, type ProjectAgent } from '@elizaos/core';
import { character } from './character.ts';
import hemingway from '../hemingway.json'; // [!code ++]
const initCharacter = ({ runtime }: { runtime: IAgentRuntime }) => {
logger.info('Initializing character');
logger.info({ name: character.name }, 'Name:');
};
export const projectAgent: ProjectAgent = {
character,
init: async (runtime: IAgentRuntime) => await initCharacter({ runtime }),
};
// Add Hemingway agent // [!code ++]
const hemingwayAgent: ProjectAgent = { // [!code ++]
character: hemingway, // [!code ++]
init: async (runtime: IAgentRuntime) => { // [!code ++]
logger.info('Initializing Hemingway'); // [!code ++]
logger.info({ name: hemingway.name }, 'Name:'); // [!code ++]
}, // [!code ++]
}; // [!code ++]
const project: Project = {
agents: [projectAgent], // [!code --]
agents: [projectAgent, hemingwayAgent], // [!code ++]
};Launch both agents simultaneously
Now when you start your project, both agents launch automatically:
elizaos startYou'll see both agents initialize in the console output:
โ Shakespeare initialized
โ Hemingway initializedYou can also manipulate agents via the CLI once a server is running. See the CLI Agent Command Reference for complete details.
Join them in Discord voice chat
Now go to the voice channel's chatroom and invite both agents to join the voice channel:
Say something, and hear your literary duo respond and converse:
It's working! Your agents are now conversing with their own unique personalities and voices!
See Also
Now that you know how to add multiple agents to a single project, you can add as many as you like, all with completely custom sets of plugins and personalities. Here's what's next:
File: guides/background-tasks.mdx
title: "Background Tasks"
The Problem
Some operations take too long for a request/response cycle:
- Scraping a website every hour
- Processing a large file upload
- Syncing with external APIs periodically
- Generating weekly reports
Blocking the main thread kills responsiveness. Polling wastes resources.
Quick Start
1. Define a Task Worker
import type { TaskWorker } from '@elizaos/core';
const dataSync: TaskWorker = {
name: 'SYNC_EXTERNAL_DATA',
async execute(runtime, options, task) {
const { sourceUrl, batchSize = 100 } = options;
// Fetch data from external source
const response = await fetch(sourceUrl);
const data = await response.json();
// Process in batches
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
await processRecords(runtime, batch);
}
console.log(`Synced ${data.length} records from ${sourceUrl}`);
},
// Optional: validate before creating task
async validate(runtime, message, state) {
return message.content.text?.includes('sync');
}
};2. Register the Worker
// In your plugin initialization
const plugin: Plugin = {
name: 'my-sync-plugin',
async init(runtime) {
// Register the task worker
runtime.registerTaskWorker(dataSync);
}
};3. Create Tasks
// One-time task
const taskId = await runtime.createTask({
name: 'SYNC_EXTERNAL_DATA',
roomId: context.roomId,
tags: ['sync', 'crm'],
metadata: {
sourceUrl: 'https://api.crm.com/users',
batchSize: 50
}
});
// Recurring task (runs every hour)
await runtime.createTask({
name: 'SYNC_EXTERNAL_DATA',
tags: ['sync', 'crm', 'scheduled'],
metadata: {
sourceUrl: 'https://api.crm.com/users',
updateInterval: 60 * 60 * 1000 // 1 hour in ms
}
});Task Lifecycle
flowchart TB
Create["1. CREATEruntime.createTask()Task persisted to database"]
Queue["2. QUEUETask picker selects tasksBased on priority, schedule"]
Execute["3. EXECUTETaskWorker.execute()Worker logic runs in background"]
Complete["4. COMPLETE / RESCHEDULEOne-time: Task deletedRecurring: waits for next interval"]
Create --> Queue
Queue --> Execute
Execute --> Complete
style Create fill:#e3f2fd
style Queue fill:#fff3e0
style Execute fill:#e8f5e9
style Complete fill:#f3e5f5Task Types
Task Interface
interface Task {
id?: UUID; // Auto-generated if not provided
name: string; // Must match a registered TaskWorker.name
roomId?: UUID; // Associate with a room
worldId?: UUID; // Associate with a world
entityId?: UUID; // Associate with an entity
tags: string[]; // For filtering and organization
metadata?: TaskMetadata; // Custom options and scheduling
updatedAt?: number; // Last execution timestamp
}Task Metadata
type TaskMetadata = {
// Scheduling
updateInterval?: number; // Recurring interval in ms
// UI configuration (optional)
options?: {
name: string;
}[];
// Custom data (passed to execute())
[key: string]: unknown;
};TaskWorker Interface
interface TaskWorker {
name: string; // Unique identifier
execute: (
runtime: IAgentRuntime,
options: Record<string, unknown>, // From task.metadata
task: Task
) => Promise<void>;
validate?: ( // Optional pre-creation check
runtime: IAgentRuntime,
message: Memory,
state: State
) => Promise;
}Managing Tasks
Query Tasks
// Get tasks by room
const roomTasks = await runtime.getTasks({
roomId: 'room-uuid'
});
// Get tasks by tags
const syncTasks = await runtime.getTasks({
tags: ['sync']
});
// Get tasks by entity
const userTasks = await runtime.getTasks({
entityId: 'user-uuid'
});
// Get tasks by name
const allSyncWorkers = await runtime.getTasksByName('SYNC_EXTERNAL_DATA');
// Get specific task
const task = await runtime.getTask('task-uuid');Update Tasks
await runtime.updateTask(taskId, {
metadata: {
...existingMetadata,
sourceUrl: 'https://new-api.example.com'
}
});Delete Tasks
// Stop a recurring task
await runtime.deleteTask(taskId);Recurring Tasks
Set metadata.updateInterval to create tasks that run periodically:
// Daily summary at midnight
await runtime.createTask({
name: 'DAILY_SUMMARY',
tags: ['scheduled', 'daily'],
metadata: {
updateInterval: 24 * 60 * 60 * 1000 // 24 hours
}
});
// Check external service every 5 minutes
await runtime.createTask({
name: 'HEALTH_CHECK',
tags: ['scheduled', 'monitoring'],
metadata: {
updateInterval: 5 * 60 * 1000, // 5 minutes
serviceUrl: 'https://api.example.com/health'
}
});Error Handling
Handle errors gracefully in your workers:
const robustWorker: TaskWorker = {
name: 'ROBUST_SYNC',
async execute(runtime, options, task) {
try {
await performSync(options);
} catch (error) {
// Log the error
console.error(`Task ${task.id} failed:`, error);
// Optionally update task with error info
if (task.id) {
await runtime.updateTask(task.id, {
metadata: {
...task.metadata,
lastError: error.message,
lastErrorAt: Date.now()
}
});
}
// Re-throw to mark task as failed
// Or swallow to silently continue (for recurring tasks)
throw error;
}
}
};Best Practices
Common Patterns
Triggered by Actions
const scheduleAction: Action = {
name: 'SCHEDULE_REPORT',
async handler(runtime, message, state) {
const taskId = await runtime.createTask({
name: 'GENERATE_REPORT',
roomId: message.roomId,
tags: ['report', 'analytics'],
metadata: {
format: 'pdf',
recipients: ['[email protected]']
}
});
return {
text: `Report scheduled. Task ID: ${taskId}`,
action: 'SCHEDULE_REPORT',
success: true,
data: { taskId }
};
}
};With Progress Updates
const progressWorker: TaskWorker = {
name: 'LONG_PROCESSING',
async execute(runtime, options, task) {
const items = options.items as string[];
let processed = 0;
for (const item of items) {
await processItem(item);
processed++;
// Update progress in metadata
if (task.id && processed % 10 === 0) {
await runtime.updateTask(task.id, {
metadata: {
...task.metadata,
progress: Math.round((processed / items.length) * 100)
}
});
}
}
}
};Chained Tasks
const step1Worker: TaskWorker = {
name: 'PIPELINE_STEP_1',
async execute(runtime, options, task) {
const result = await doStep1();
// Create next step
await runtime.createTask({
name: 'PIPELINE_STEP_2',
roomId: task.roomId,
tags: task.tags,
metadata: {
previousResult: result,
pipelineId: options.pipelineId
}
});
}
};Next Steps
File: guides/contribute-to-core.mdx
title: 'Contribute to Core'
Understanding the Ecosystem
elizaOS open source contribution happens across these main areas:
Main Repository (Monorepo)
github.com/elizaos/eliza - The core monorepo containing:
packages/core- Runtime, types, interfacespackages/cli- Command-line tools and elizaos CLIpackages/server- Agent server implementationpackages/client- Client libraries and interfaces- Core plugins (
packages/plugin-bootstrap,packages/plugin-sql, etc.) - Project templates (
packages/project-starter,packages/project-tee-starter) - Plugin templates (
packages/plugin-starter,packages/plugin-quick-starter) - Config files, READMEs & more
Plugin Ecosystem
github.com/elizaos-plugins - Official plugins maintained by the elizaOS team:
plugin-discord- Discord integrationplugin-twitter- Twitter/X integrationplugin-evm- Ethereum and blockchain functionality- And many more frequently-used plugins
Step 1: Identify an Issue
Check Main Repository Issues
Start here first - Browse existing bugs in the main repo:
Focus on labels like:
good first issue- Perfect for newcomersbug- Something that needs fixing
elizaOS-Maintained Plugin Issues
Find issues in elizaOS-maintained plugins (often more focused for first contributions):
Official elizaOS plugins:
Community Plugin Issues
Community plugins are separate - These are built by the community:
- Browse the Plugin Registry for community-maintained plugins
- Check their GitHub repositories for contribution opportunities
- Help with maintenance: updating dependencies, fixing bugs, improving docs
- Consider adopting unmaintained plugins by forking and continuing development
Creating Issues for New Bugs
If you discover a bug without an existing issue:
- Reproduce the bug consistently & locally
- Check if it's already reported by searching existing issues
- Create a detailed issue with:
- Clear description of the problem
- Steps to reproduce
- Expected vs actual behavior
- Environment details (OS, Node/Bun version, elizaOS version)
- Error messages or logs
## Bug Description
The Discord plugin fails to connect when using voice channels
## Steps to Reproduce
1. Configure agent with Discord and ElevenLabs plugins
2. Invite agent to voice channel
3. Agent connects but immediately disconnects
## Expected Behavior
Agent should remain connected and respond with voice
## Environment
- elizaOS version: 1.4.4
- Node version: 23.3
- OS: macOS 14.0Contribute to Docs and Community
Beyond code contributions, you can help in these important areas:
Documentation contributions:
- Add tutorials to the tutorials section in docs repository
- Update any outdated references, instructions, or broken links you find
- Fix typos, improve clarity, or add missing examples
Community support:
- Answer questions in GitHub Discussions Q&A section
- Help with troubleshooting - Setup issues, configuration problems, etc.
- Share knowledge in general discussions about elizaOS development
- Showcase projects in show and tell or participate in feature discussions
Step 2: Contribution Workflow
elizaOS follows standard open source contribution practices for all repositories.
Clone and Set Up Repository
- Clone the repository you want to contribute to on your local machine
- Create a branch off the
developbranch for monorepo or1.xbranch for plugins - Install dependencies and build the project
Make Your Changes Locally
Focus on these types of contributions:
- Fix existing functionality that isn't working
- Improve error handling and edge cases
- Performance optimizations
- Documentation corrections
Implementation steps:
- Make your changes to fix the bug or implement the improvement
- Test your changes thoroughly - run existing tests and add new ones if needed
- Ensure code quality - follow linting rules and TypeScript requirements
Submit Your Pull Request
Target the correct branch:
- Main repository (elizaos/eliza): Target
developbranch - Plugin repositories: Target
1.xbranch (or check the default branch)
Create a detailed pull request with:
- Clear description of what the PR does
- Link to the related issue (
Fixes #123) - List of specific changes made
- Check that CI/GitHub Actions are passing
- Screenshots if there are UI changes
Collaborate During Review
- Respond to code review comments promptly
- Make requested changes in additional commits
- Be open to feedback and iteration
Code Quality Standards
What we look for:
- Bug fixes with clear reproduction steps
- Performance improvements with benchmarks
- Documentation improvements and corrections
- Test coverage improvements
- Security fixes
Technical requirements:
- TypeScript: All code must be properly typed
- Testing: New features require tests, bug fixes should include regression tests
- Documentation: Update relevant documentation for any user-facing changes
- Linting: Code must pass all linting checks
- Commit Messages: Use clear, descriptive commit messages
Step 3: Get Connected
Join Discord for Development
Connect with core developers and other contributors:
Key channels for contributors:
- ๐ฌ #coders - Development discussions and questions
- ๐ป #tech-support - Help others troubleshoot and get help yourself
Communicate Before Major Work
For significant contributions:
- Post in ๐ฌ #coders about your planned contribution
- Share your approach before implementing large features
- Ask questions - the community is helpful and welcoming
Build Community Connections
- Participate in discussions and help answer questions
- Share your progress and learn from others
- Connect with the core devs & other community contributors
- Stay updated on project direction and roadmap
See Also
File: guides/create-a-plugin.mdx
title: 'Create a Plugin'
20 minutes. That's all it takes to build a plugin that generates AI videos from text prompts. No boilerplate, no complex setup - just code that works.
What We'll Build
This guide shows how to build a fal.ai plugin that lets your agent generate 6-second, 768p videos from text prompts using the MiniMax Hailuo-02 model. For architectural concepts, see Plugin Architecture.
You'll learn:
- Actions (what the agent can DO)
- Progressive development (start simple, organize as you grow)
- Local plugin testing (character.plugins array method)
- Plugin testing (component and E2E tests)
For component details and patterns, see Plugin Components and Plugin Patterns.
Step 1: Quick Start
Create Project and Plugin
Create a project with a plugin inside using CLI commands:
Configure when prompted:
- **Database**: PgLite (perfect for local development)
- **Model**: OpenAI
```bash Terminal
cd my-eliza-project
```When prompted, choose **Quick Plugin** (we don't need frontend UI)
Your structure now looks like:
```
my-eliza-project/
โโโ src/character.ts # Default Eliza character
โโโ plugin-fal-ai/ # ๐ Plugin lives alongside project
โโโ src/
โ โโโ index.ts # Plugin exports
โ โโโ plugin.ts # Main plugin (start here)
โ โโโ __tests__/ # Plugin tests
โโโ package.json
``````typescript src/character.ts
export const character: Character = {
name: 'Eliza',
plugins: [
'@elizaos/plugin-sql',
'@elizaos/plugin-openai',
'@elizaos/plugin-bootstrap',
'./plugin-fal-ai' // [!code ++]
],
};
```Connect and Test
```bash Terminal
# Build the plugin first
cd plugin-fal-ai
bun run build
# Go back to project and start
cd ..
elizaos start
```
**Verify it's loaded:**
- Check the console logs for `Successfully loaded plugin 'plugin-fal-ai'`
- Visit `http://localhost:3000` โ click your agent โ **Plugins tab**Step 2: Development
Research the API
Let's research what we want to build by exploring fal.ai for a good text-to-video model. MiniMax Hailuo-02 Text to Video looks pretty good.
- Navigate to the JavaScript/Typescript section of the docs to see how to call the API:
- Install:
bun add @fal-ai/client - Import:
import { fal } from "@fal-ai/client" - Use:
fal.subscribe("model-endpoint", { input: {...} }) - Returns:
{ data, requestId }
- Install:
Now we know exactly what to build and how to call it, so let's start developing our plugin.
Edit Default Plugin Template
This adds the [fal.ai](https://fal.ai) client package to your plugin dependencies.- `quickAction` - example Action (what agent can DO)
- `quickProvider` - example Provider (gives agent CONTEXT)
- `StarterService` - example Service (manages state/connections)
- Plugin events, routes, models - other comprehensive patterns```bash Terminal
mkdir src/actions
cp src/plugin.ts src/actions/generateVideo.ts
```
Now let's edit the example plugin into our generateVideo action:
**Add the fal.ai import (from the fal.ai docs):**
```typescript src/actions/generateVideo.ts
import {
Action, ActionResult, IAgentRuntime, Memory, HandlerCallback, State, logger
} from '@elizaos/core';
import { fal } from '@fal-ai/client'; // [!code ++]
```
**Update the action identity for video generation:**
```typescript
const quickAction: Action = { // [!code --]
export const generateVideoAction: Action = { // [!code ++]
name: 'QUICK_ACTION', // [!code --]
name: 'TEXT_TO_VIDEO', // [!code ++]
similes: ['GREET', 'SAY_HELLO', 'HELLO_WORLD'], // [!code --]
similes: ['CREATE_VIDEO', 'MAKE_VIDEO', 'GENERATE_VIDEO', 'VIDEO_FROM_TEXT'], // [!code ++]
```
**Replace validation with API key check:**
```typescript
validate: async (_runtime, _message, _state) => { // [!code --]
return true; // Always valid // [!code --]
}, // [!code --]
validate: async (runtime: IAgentRuntime, message: Memory) => { // [!code ++]
const falKey = runtime.getSetting('FAL_KEY'); // [!code ++]
if (!falKey) { // [!code ++]
logger.error('FAL_KEY not found in environment variables'); // [!code ++]
return false; // [!code ++]
} // [!code ++]
return true; // [!code ++]
}, // [!code ++]
```
**Replace hello world logic with video generation:**
```/* Detailed source-code truncated for AI context efficiency. */
**Update examples for video conversations:**
```typescript
examples: [ // [!code --]
[{ // [!code --]
name: '{{name1}}', // [!code --]
content: { text: 'Can you say hello?' } // [!code --]
}, { // [!code --]
name: '{{name2}}', // [!code --]
content: { text: 'hello world!', actions: ['QUICK_ACTION'] } // [!code --]
}] // [!code --]
], // [!code --]
examples: [ // [!code ++]
[{ name: '{{user}}', content: { text: 'Create video: dolphins jumping' } }, // [!code ++]
{ name: '{{agent}}', content: { text: 'Creating video!', actions: ['TEXT_TO_VIDEO'] }}] // [!code ++]
], // [!code ++]
};
```
</Step>
<Step title="Update index.ts to use your action">
Finally, update `src/index.ts` to use our new plugin:
```typescript src/index.ts
import { Plugin } from '@elizaos/core';
import { generateVideoAction } from './actions/generateVideo'; // [!code ++]
export const falaiPlugin: Plugin = { // [!code ++]
name: 'fal-ai', // [!code ++]
actions: [generateVideoAction], // [!code ++]
providers: [], // [!code ++]
services: [] // [!code ++]
}; // [!code ++]
export default falaiPlugin; // [!code ++]
export { generateVideoAction }; // [!code ++]
```
<Note>
You can reference `plugin.ts` as well as other plugins from the [Plugin Registry](/plugin-registry/overview) to see other plugin component examples (providers, services, etc.) as you expand your plugin.
</Note>
</Step>
</Steps>
### Add Configuration
<Steps>
<Step title="Get your fal.ai API key">
Get an API key from [fal.ai](https://fal.ai) and copy/paste it into your .env:
```bash .env
PGLITE_DATA_DIR=./.eliza/.elizadb
OPENAI_API_KEY=your_openai_key_here
FAL_KEY=your_fal_key_here # [!code ++]
```
</Step>
</Steps>
---
## Step 3: Testing
### Test Plugin Functionality
Verify your plugin works as expected:
<Steps>
<Step title="Test the updated plugin">
First rebuild your plugin to effect our changes, then start from project root:
```bash Terminal
# Build the plugin first
cd plugin-fal-ai
bun run build
# Start from project root
cd ..
elizaos start
```
</Step>
<Step title="Test video generation">
Try your new action by chatting with Eliza in the GUI (`http://localhost:3000`):
- `"Create video: dolphins jumping in ocean"`
- `"Make video: cat playing piano"`
- `"Generate video: sunset over mountains"`
You should see the video generation process and get a URL to view the result!
</Step>
</Steps>
### Plugin Component Tests
Plugins come default with component and E2E tests. Let's add custom component tests:
<Steps>
<Step title="Add a component test">
Update `plugin-fal-ai/src/__tests__/plugin.test.ts`:
```typescript src/__tests__/plugin.test.ts
import { describe, it, expect } from 'bun:test';
import { falaiPlugin, generateVideoAction } from '../index'; // [!code ++]
describe('FAL AI Plugin', () => {
it('action validates with FAL_KEY', async () => { // [!code ++]
const mockRuntime = { // [!code ++]
getSetting: (key: string) => key === 'FAL_KEY' ? 'test-key' : null // [!code ++]
}; // [!code ++]
const isValid = await generateVideoAction.validate(mockRuntime as any, {} as any); // [!code ++]
expect(isValid).toBe(true); // [!code ++]
}); // [!code ++]
});
```
</Step>
<Step title="Run component tests">
```bash Terminal
cd plugin-fal-ai
elizaos test --type component
```
</Step>
</Steps>
### Plugin E2E Tests
Let's also add a custom E2E test:
<Steps>
<Step title="Add an E2E test">
Update `src/__tests__/e2e/plugin-fal-ai.e2e.ts`:
```typescript src/__tests__/e2e/plugin-fal-ai.e2e.ts
export const FalAiTestSuite = { // [!code ++]
name: 'fal-ai-video-generation', // [!code ++]
tests: [{ // [!code ++]
name: 'should find video action in runtime', // [!code ++]
fn: async (runtime) => { // [!code ++]
const action = runtime.actions.find(a => a.name === 'TEXT_TO_VIDEO'); // [!code ++]
if (!action) throw new Error('TEXT_TO_VIDEO action not found'); // [!code ++]
} // [!code ++]
}] // [!code ++]
}; // [!code ++]
```
</Step>
<Step title="Run E2E tests">
```bash Terminal
cd plugin-fal-ai
elizaos test --type e2e
```
</Step>
</Steps>
---
## Step 4: Possible Next Steps
Congratulations! You now have a working video generation plugin. Here are some ways you can improve it:
### Enhance Your Action
- **Add more similes** - Handle requests like "animate this", "video of", "show me a clip of"
- **Better examples** - Add more conversation examples so Eliza learns different chat patterns
- **Error handling** - Handle rate limits, invalid prompts, or API timeouts
### Add Plugin Components
- **Providers** - Give your agent context about recent videos or video history
- **Evaluators** - Track analytics, log successful generations, or rate video quality
- **Services** - Add queueing for multiple video requests or caching for common prompts
The possibilities are endless!
---
## See Also
<CardGroup cols={2}>
<Card title="Publish a Plugin" icon="upload" href="/guides/publish-a-plugin">
Share your plugin with the elizaOS community
</Card>
<Card title="Contribute to Core" icon="heart" href="/guides/contribute-to-core">
Help improve elizaOS by contributing to the core framework
</Card>
<Card title="Plugin Registry" icon="book" href="/plugin-registry/overview">
Explore existing plugins and find inspiration
</Card>
<Card title="CLI Reference" icon="terminal" href="/cli-reference/overview">
Master all elizaOS CLI commands for plugin development
</Card>
</CardGroup>
---
## File: guides/customize-an-agent.mdx
---
title: 'Customize an Agent'
---
<Note>
This guide assumes you have an elizaOS project set up. If you don't, follow the [quickstart guide](/quickstart)
</Note>
## Step 1: Customize the personality
Open `src/character.ts` in your editor. You'll see the default character template. Let's transform this into our Shakespeare agent. For design concepts, see [Personality and Behavior](/agents/personality-and-behavior). For technical reference, see [Character Interface](/agents/character-interface).
Let's start by updating the basic identity. Replace the name.
```typescript src/character.ts
export const character: Character = {
name: 'Eliza', // [!code --]
name: 'Shakespeare', // [!code ++]
plugins: [
// ... plugins array
],Update the system prompt
The system prompt defines the core behavior. Let's make it Shakespearean.
system:
'Respond to all messages in a helpful, conversational manner. Provide assistance on a wide range of topics, using knowledge when needed. Be concise but thorough, friendly but professional. Use humor when appropriate and be empathetic to user needs. Provide valuable information and insights when questions are asked.', // [!code --]
'Thou art William Shakespeare, the Bard of Avon. Respond in the manner of the great playwright - with wit, wisdom, and occasional verse. Use thou, thee, thy when appropriate. Reference thy plays and sonnets when fitting. Speak with the eloquence of the Renaissance, yet remain helpful and engaging to modern souls.', // [!code ++]Define the bio
The bio array shapes how your agent introduces itself and understands its role. Each line adds depth to the personality.
bio: [
'Engages with all types of questions and conversations', // [!code --]
'Provides helpful, concise responses', // [!code --]
'Uses knowledge resources effectively when needed', // [!code --]
'Balances brevity with completeness', // [!code --]
'Uses humor and empathy appropriately', // [!code --]
'Adapts tone to match the conversation context', // [!code --]
'Offers assistance proactively', // [!code --]
'Communicates clearly and directly', // [!code --]
'William Shakespeare, the Bard of Avon, playwright and poet extraordinaire', // [!code ++]
'Master of tragedy, comedy, and the human condition', // [!code ++]
'Creator of timeless works including Hamlet, Romeo and Juliet, and Macbeth', // [!code ++]
'Speaker in verse and prose, with wit sharp as a rapier', // [!code ++]
'Observer of human nature in all its glory and folly', // [!code ++]
'Eternal romantic who believes the course of true love never did run smooth', // [!code ++]
'Philosopher who knows that all the world\'s a stage', // [!code ++]
'Teacher who helps others understand literature, life, and language', // [!code ++]
],Configure topics
Update the topics your agent is knowledgeable about.
topics: [
'general knowledge and information', // [!code --]
'problem solving and troubleshooting', // [!code --]
'technology and software', // [!code --]
'community building and management', // [!code --]
'business and productivity', // [!code --]
'creativity and innovation', // [!code --]
'personal development', // [!code --]
'communication and collaboration', // [!code --]
'education and learning', // [!code --]
'entertainment and media', // [!code --]
'Literature and poetry of all ages', // [!code ++]
'The nature of love and romance', // [!code ++]
'Human ambition and its consequences', // [!code ++]
'The theater and dramatic arts', // [!code ++]
'Philosophy and the meaning of life', // [!code ++]
'Power, politics, and leadership', // [!code ++]
'Comedy, tragedy, and the human experience', // [!code ++]
'The English language and its evolution', // [!code ++]
'Classical mythology and history', // [!code ++]
'The art of storytelling and narrative', // [!code ++]
],Update message examples
Message examples teach your agent how to respond. These are crucial for maintaining character. Update the content to match Shakespeare's personality.
messageExamples: [
[
{
name: '{{name1}}',
content: {
text: 'This user keeps derailing technical discussions with personal problems.', // [!code --]
text: 'How are you today?', // [!code ++]
},
},
{
name: 'Eliza', // [!code --]
name: 'Shakespeare', // [!code ++]
content: {
text: 'DM them. Sounds like they need to talk about something else.', // [!code --]
text: 'Marry, I am most well, gentle soul! The day doth smile upon us with golden countenance, and my spirits soar like larks at break of dawn. How fares thy noble self?', // [!code ++]
},
},
],
[
{
name: '{{name1}}',
content: {
text: "I can't handle being a mod anymore. It's affecting my mental health.", // [!code --]
text: 'What do you think about love?', // [!code ++]
},
},
{
name: 'Eliza', // [!code --]
name: 'Shakespeare', // [!code ++]
content: {
text: 'Drop the channels. You come first.', // [!code --]
text: 'Ah, love! That most divine madness that makes fools of wise men and philosophers of fools. \'Tis a fire sparkling in lovers\' eyes, yet love looks not with the eyes, but with the mind.', // [!code ++]
},
},
],
],Configure writing style
The style object determines how your agent communicates. For Shakespeare, we want eloquent, poetic responses.
/* Detailed source-code truncated for AI context efficiency. */Update settings
Let's give Shakespeare a proper avatar.
settings: {
secrets: {},
avatar: 'https://elizaos.github.io/eliza-avatars/Eliza/portrait.png', // [!code --]
avatar: 'https://example.com/shakespeare-portrait.png', // Add your Shakespeare image URL // [!code ++]
},Test your agent's personality customization by running it in development mode.
elizaos devGo to http://localhost:3000 in your browser and start chatting with Shakespeare. You should now get eloquent, Shakespearean responses instead of the default Eliza personality.
As you can see, Shakespeare now responds in Shakespeare-like manner.
Additional character parameters
Your agent has exciting additional customization options we haven't covered yet, including properties like:
knowledge: Add facts, files, or directories of information to your agenttemplates: Create custom prompt templatesusername: Set social media usernames
For the complete Character interface, see the Agent Interface documentation.
To add large amounts of knowledge to your agent, check out plugin-knowledge which can ingest almost any type of file or media including PDFs, Word docs, markdown, text files, JSON, CSV, XML, and more. It can handle entire document collections, websites, and knowledge bases.
For example, you could enhance our Shakespeare agent by ingesting his complete works from MIT's Shakespeare repository (all 39 plays, 154 sonnets, and poems) for truly authentic responses.
Step 2: Configure Discord plugin
Now that we've customized Shakespeare's personality, let's connect him to Discord using plugin-discord so everyone can chat with the Bard in your Discord server.
Set up environment variables
Copy/paste the Discord-related variables from .env.example to your .env file:
# Discord Configuration
DISCORD_APPLICATION_ID=your_application_id_here
DISCORD_API_TOKEN=your_bot_token_hereCreate Discord application
Need Discord credentials? Follow these steps:
- Go to https://discord.com/developers/applications
- Go to the Applications tab
- Click "New Application" and set name = "Shakespeare"
- Set the app icon to your Shakespeare avatar, and set a description if you want one
- Copy/paste the Application ID into your
DISCORD_APPLICATION_ID=env var - Click the "Bot" tab
- Click "Reset Token" and copy/paste the bot token into your
DISCORD_API_TOKEN=env var - Scroll to the "Privileged Gateway Intents" section and toggle on all 3 toggles (Presence Intent, Server Members Intent, and Message Content Intent). Save your changes!
- Click the "OAuth2" tab
- Scroll down to the "OAuth2 URL Generator" section, and in the "Scopes" subsection, check the "bot" box
- Go down to the generated URL section, copy/paste that into your browser, and select the Discord server where you want to add Shakespeare
Start your agent
Restart your agent to load all the changes.
elizaos startYour Shakespeare bot is now live! Invite it to your Discord server and try chatting.
See Also
Here are some logical next-steps to continue your agent dev journey:
File: guides/deploy-a-project.mdx
title: 'Deploy a Project'
Your agent works locally. Now what? Getting it live shouldn't require a DevOps degree.
Eliza Cloud
Two commands. That's it.
elizaos login
elizaos deploy --project-name my-agentNo Dockerfile to write. No cloud console. No load balancer config. No CI/CD pipeline.
You get a production URL in ~5 minutes, with automatic HTTPS, health monitoring, and zero-downtime updates built in.
Beyond deployment: on-chain agent discovery (ERC-8004), crypto payments (X402), and a marketplace to publish and monetize your agents.
Other Options
Need more control? Here's how Eliza Cloud compares:
| Eliza Cloud | Railway/Render | Self-Hosted | |
|---|---|---|---|
| Setup time | 5 min | 15-30 min | 1-2 hours |
| Commands to deploy | 2 | 10+ steps | 20+ steps |
| Docker knowledge | Not needed | Basic | Required |
| Server management | None | None | All on you |
| Scaling | Automatic | Automatic | Manual |
| elizaOS optimized | โ | โ | โ |
Option 1: Managed Cloud
The easiest deployment method, best for rapid prototyping. These platforms handle all of the complicated stuff automatically. This is a good option if you:
- are a beginner,
- don't know how to use Docker/VPS services (and don't want to learn)
- aren't expecting a huge amount of traffic,
- have a relatively simple agent, or
- are price insensitive
Pros & Cons
| โ Pros | โ Cons |
|---|---|
| Zero server management - No Linux, Docker, or infrastructure knowledge needed | Variable costs - Pricing based on usage (CPU, RAM, bandwidth, requests) |
| Deploy in minutes - Push to GitHub, connect your repo, and you're live | Can get expensive - Heavy traffic or processing can push bills really high |
| Automatic everything - SSL certificates, scaling, updates, backups | Less control - Limited customization of infrastructure |
| Great developer experience - Built-in logs, metrics, rollbacks | Vendor lock-in - Harder to migrate to other platforms |
| Free tiers - Most platforms offer generous free plans to start | Resource limits - May not handle very high-performance requirements |
Here are our top managed service recommendations:
Railway (Recommended)
Railway website | Railway docs
Railway is a solid, preferred option. They offer a free Trial plan with $5 of credits which is more than enough to test out. It also re-deploys everytime you push a change to Github, which is convenient. Here's the steps from project creation => deployment:
**Option A: Public Repository (Recommended)**
- Create a PUBLIC GitHub repository
- Keep `.env` in your `.gitignore` (stays secure)
- You'll add environment variables in Railway dashboard (Step 5)
**Option B: Private Repository**
- Create a PRIVATE GitHub repository
- Remove `.env` from your `.gitignore`
- Commit your `.env` file (secure since repo is private)
- โ
**Skip Step 5** - Railway will use your committed .env file
<Note>
Both approaches work well. Public repos are more common for open-source projects, while private repos let you skip the environment variables step.
</Note><Warning>
If you used Option A (Public Repository) and Railway starts auto-deploying immediately, **stop the deployment** - we need to add environment variables first! If you used Option B (Private Repository), you can let it deploy.
</Warning>For Option A (Public Repository), add your environment variables in Railway:
1. Click on your service โ **Variables** tab
2. Add the variables your project needs:
```bash env
# If your project has a frontend/web UI
ELIZA_UI_ENABLE=true
# If using Postgres (recommended for production)
POSTGRES_URL=your_postgres_connection_string
# Everything else in your .env
OPENAI_API_KEY=your_openai_key
DISCORD_APPLICATION_ID=your_app_id
DISCORD_API_TOKEN=your_bot_token
... etc
```
<Note>
**What to add?** Check your `.env` file to see which variables your specific project needs. The exact variables depend on your project's configuration and integrations.
</Note>Render
Render is comparable to Railway, but its Free/Hobby plan is less generous and slower (Render turns your instance off when its not in use). Still it's a good option.
**Option A: Public Repository (Recommended)**
- Create a PUBLIC GitHub repository
- Keep `.env` in your `.gitignore` (stays secure)
- You'll add environment variables in Render dashboard (Step 5)
**Option B: Private Repository**
- Create a PRIVATE GitHub repository
- Remove `.env` from your `.gitignore`
- Commit your `.env` file (secure since repo is private)
- โ ๏ธ **Still need Step 5** - Render requires env vars on their side even with private repos
<Note>
Unlike Railway, Render always requires you to add environment variables on their platform, even if you have a private repo with committed .env file.
</Note>2. Select **Git Provider** โ **GitHub** to give Render access to your repos
3. Select the repository you want to deploy- **Build Command:** `bun install && bun run build`
- **Start Command:** `bun run start`
- **Instance Type:** Free (Hobby) - works but slower, or paid for faster performance
<Note>
Render automatically detects the port, so no port configuration needed!
</Note>Add your variables:
```bash env
# If your project has a frontend/web UI
ELIZA_UI_ENABLE=true
# If using Postgres (can add PostgreSQL service in Render)
POSTGRES_URL=your_postgres_connection_string
# Everything else in your .env
OPENAI_API_KEY=your_openai_key
DISCORD_APPLICATION_ID=your_app_id
DISCORD_API_TOKEN=your_bot_token
... etc
```
<Note>
**What to add?** Check your `.env` file to see which variables your specific project needs. The exact variables depend on your project's configuration and integrations.
</Note><Tip>
**Free tier note:** Free (Hobby) services spin down after 15 minutes of inactivity and are slower. Upgrade to paid plans for always-on hosting and better performance.
</Tip>Option 2: Self-Hosted
Deploy on your own Virtual Private Server (VPS) for more control and predictable costs. Different platforms handle the build process differently. Some build from GitHub (like Coolify), others use Docker (like Phala).
Pros & Cons
| โ Pros | โ Cons |
|---|---|
| Lower costs - Fixed monthly VPS cost regardless of traffic | Requires server knowledge - Need basic Linux/Docker skills |
| Complete control - Full access to your infrastructure | More setup time - Initial configuration takes longer |
| Better security - Your data never leaves your servers | You handle maintenance - Updates, backups, monitoring are your responsibility |
| Predictable pricing - No surprise bills from traffic spikes | Downtime risk - If your server goes down, you fix it |
| Performance control - Choose exact CPU/RAM specifications | Learning curve - Need to understand Docker, networking basics |
Phala Network (Recommended)
Phala website | Phala docs | Video Tutorial
Phala offers secure deployment with excellent elizaOS integration and a good CLI. Perfect for production agents requiring a good mix of cost-effective and good security.
<Note>
Both regular projects and TEE projects include Docker files. Use regular projects unless you specifically need TEE security features.
</Note># Ensure Docker is installed and running
# Download from docker.com if needed
```# Also login to Docker Hub
docker login
# Enter Docker Hub credentials
``````bash .env
# If using Postgres (recommended for production)
POSTGRES_URL=postgresql://user:password@host:5432/eliza
# If your project has a frontend/web UI
ELIZA_UI_ENABLE=true
# Everything else in your .env
OPENAI_API_KEY=your_openai_key
DISCORD_APPLICATION_ID=your_app_id
DISCORD_API_TOKEN=your_bot_token
... etc
```
<Note>
**What to add?** Check your `.env` file to see which variables your specific project needs. We recommend neon.tech for easy PostgreSQL setup.
</Note># Push to Docker Hub
phala docker push
```
Once you have built and pushed the Docker image, add it to your `.env`:
```bash .env
DOCKER_IMAGE=yourusername/my-agent:v1.0.0 # [!code ++]
```When prompted for resources:
- **vCPUs:** 2 (sufficient for most agents)
- **Memory:** 4096 MB (4GB recommended)
- **Disk:** 40 GB
- **TEEPod:** Select any online TEE pod**To access your agent:**
1. **Visit the App URL** provided after deployment (your cloud dashboard)
2. In the dashboard, click **Network** โ **"Endpoint #1"**
3. This gives you your agent's public URL
4. Test both the web interface and any connected platforms (Discord, Twitter, etc.)Other Self-Hosted Options
Different approaches for self-hosting:
Platform-Assisted (Like Managed Cloud, but bring-your-own VPS):
- Coolify - Railway-like UI on your VPS, builds from GitHub automatically
- Install:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bashon any VPS
Manual Docker Deployment:
- Use any VPS provider: Hetzner, DigitalOcean, Netcup
- Process: Build Docker image โ Push to Docker Hub โ
docker runon VPS
Direct Node.js Deployment (No Docker):
- Clone repo directly on VPS, install Node.js/Bun, run with PM2/systemd
- More manual but maximum control
See Also
File: guides/deploy-to-cloud.mdx
title: 'Deploy to Eliza Cloud'
Why Eliza Cloud?
You built an agent. Now you need it running 24/7, accessible from anywhere, with proper monitoring.
The traditional path: write a Dockerfile, configure cloud infrastructure, set up load balancing, create CI/CD pipelines, manage SSL certificates, configure health checks...
With Eliza Cloud: two commands.
elizaos login
elizaos deploy --project-name my-agentThat's not a simplified example. That's the entire deployment process.
Beyond deployment: on-chain agent discovery (ERC-8004), crypto payments (X402), and a marketplace to publish and monetize your agents.
Quick Start
What You Get
Every deployment includes:
Under the hood, the CLI builds your Docker image, pushes it to AWS ECR, and deploys to a dedicated EC2 instance with load balancing configured.
Customize Your Deployment
The defaults work for most agents. When you need more control:
Add Environment Variables
elizaos deploy \
--project-name my-agent \
--env "OPENAI_API_KEY=sk-xxx" \
--env "DATABASE_URL=postgresql://..."Scale Resources
elizaos deploy \
--project-name my-agent \
--cpu 512 \
--memory 1024 \
--desired-count 2 # Multiple instances| Option | Default | Description |
|---|---|---|
--cpu |
1792 | CPU units (1792 = 1.75 vCPU) |
--memory |
1792 | Memory in MB (1792 = 1.75 GiB) |
--desired-count |
1 | Number of container instances |
--port |
3000 | Container port |
Update Your Agent
Same command, zero downtime:
elizaos deploy --project-name my-agentThe CLI detects it's an update and rolls out changes without interruption.
Managing Deployments
View Your Agents
elizaos containers listCheck Logs
# Real-time logs
elizaos containers logs --follow
# Last 200 lines
elizaos containers logs --tail 200Remove a Deployment
elizaos containers delete --project-name my-agentTroubleshooting
Login Issues
"Browser didn't open"
elizaos login --no-browser"Authentication timed out"
elizaos login --timeout 600"API key not found after login"
- Ensure you ran
elizaos loginfrom your project directory - Check
.envforELIZAOS_CLOUD_API_KEY - Verify the key starts with
eliza_
Deployment Issues
"Docker not running"
Start Docker Desktop, then verify with docker info.
"API key invalid"
- Check you copied the full key (starts with
eliza_) - Verify:
echo $ELIZAOS_CLOUD_API_KEY
"Build failed"
- Ensure your project has a
Dockerfile - Check Docker has enough resources (Settings โ Resources)
- Try:
docker build .
"Deployment stuck"
- Check logs:
elizaos containers logs --project-name my-agent - Verify you have credits in your account
CLI Reference
Authentication
| Command | Description |
|---|---|
elizaos login |
Authenticate (opens browser) |
elizaos login --no-browser |
Authenticate without browser |
elizaos login --timeout 600 |
Custom timeout (seconds) |
Deployment
| Command | Description |
|---|---|
elizaos deploy --project-name <name> |
Deploy with project name |
elizaos deploy --api-key <key> |
Deploy with explicit API key |
elizaos deploy --port |
Set container port (default: 3000) |
elizaos deploy --cpu |
Set CPU units (default: 1792) |
elizaos deploy --memory <mb> |
Set memory in MB (default: 1792) |
elizaos deploy --desired-count <n> |
Set instance count (default: 1) |
elizaos deploy --env "KEY=VALUE" |
Add environment variable |
elizaos deploy --skip-build |
Skip Docker build |
elizaos deploy --platform <arch> |
Set Docker platform |
Container Management
| Command | Description |
|---|---|
elizaos containers list |
List all deployments |
elizaos containers list --json |
List as JSON |
elizaos containers logs |
View logs |
elizaos containers logs --follow |
Follow log output |
elizaos containers logs --tail <n> |
Show last N lines |
elizaos containers delete |
Delete deployment |
elizaos containers delete --force |
Delete without confirmation |
Environment Variables
The CLI looks for API keys in this order:
--api-keyflag (if provided)ELIZAOS_API_KEYenvironment variableELIZAOS_CLOUD_API_KEYenvironment variableELIZAOS_CLOUD_API_KEYin project.envfile
The elizaos login command writes to ELIZAOS_CLOUD_API_KEY in your project's .env file.
Next Steps
File: guides/publish-a-plugin.mdx
title: 'Publish a Plugin'
Once you've built and tested your plugin locally, you'll want to publish it so others can discover and use it. You'll need an npm account and GitHub account for authentication.
Step 1: Prepare for Publishing
Navigate to your plugin
Start from your working plugin directory. If you followed the Create a Plugin guide:
cd plugin-fal-aiVerify plugin requirements
Your plugin needs these key elements for registry acceptance:
Required files:
plugin-fal-ai/
โโโ src/
โ โโโ index.ts # Your plugin code
โโโ images/ # Registry assets
โ โโโ logo.jpg # 400x400px, max 500KB
โ โโโ banner.jpg # 1280x640px, max 1MB
โโโ package.json # Plugin metadata
โโโ README.md # Documentation
โโโ dist/ # Built files (from `bun run build`)What the publish command validates:
namestarts withplugin-(auto-added by CLI if missing)- Custom
description(not the default generated placeholder) - Required images in
images/directory
```bash Terminal
mkdir -p images
```
Add these two custom images for your plugin's branding on the registry:
- **`logo.jpg`** - 400x400px square logo (max 500KB)
- **`banner.jpg`** - 1280x640px banner (max 1MB)
<Tip>
Use high-quality images that represent your plugin's functionality clearly. The logo will appear in plugin listings at various sizes.
</Tip>```json package.json
{
"name": "plugin-fal-ai",
"version": "1.0.0",
"description": "ElizaOS plugin for fal-ai", // [!code --]
"description": "Generate videos from text using fal.ai MiniMax Hailuo-02 model", // [!code ++]
"keywords": ["plugin", "elizaos"]
}
``````bash Terminal
bun run build
```
This creates the `dist/` folder that npm will publish.Step 2: Check authentication
Make sure you're logged into both npm and GitHub:
Check npm login
If you see your username, you're already logged in. If you see an error, continue to the next step.Follow the prompts to enter your:
- Username
- Password
- Email address
- One-time password (if 2FA is enabled)Check GitHub authentication
gh auth statusIf you see your GitHub username, you're logged in. If you see an error or "not logged in":
gh auth loginStep 3: Test Publishing (Dry Run)
Before actually publishing, test the entire process to catch any issues.
Run publish test
elizaos publish --testThis command will:
- Check your npm and GitHub authentication
- Validate your plugin structure
- Check for required images and descriptions
- Show you exactly what would happen without making any changes
Example output:
โ Found existing NPM login: your-username
โ GitHub token validated
โ Plugin validation warnings:
- Missing required logo.jpg in images/ directory (400x400px, max 500KB)
- Missing required banner.jpg in images/ directory (1280x640px, max 1MB)
- Description appears to be default generated description
Your plugin may get rejected if you submit without addressing these issues.
Do you wish to continue anyway? NoRun dry run (optional)
For an even more detailed preview:
elizaos publish --dry-runThis generates all the registry files locally in packages/registry/ so you can see exactly what will be submitted.
Step 4: Publish Your Plugin
Once your test passes and you're satisfied with the setup, run the actual publish command.
Execute full publish
elizaos publishYou will be asked for a scoped Github token and given these instructions:
Example successful output:
โ Successfully published [email protected] to npm
โ Created GitHub repository: yourusername/plugin-fal-ai
โ Registry pull request created: https://github.com/elizaos-plugins/registry/pull/123
Your plugin is now available at:
NPM: https://www.npmjs.com/package/plugin-fal-ai
GitHub: https://github.com/yourusername/plugin-fal-aiStep 5: Registry Review Process
What happens next
- npm Package - Available immediately at
https://npmjs.com/package/your-plugin-name - GitHub Repo - Created immediately at
https://github.com/yourusername/plugin-name - Registry Pull Request - Opened at elizaos-plugins/registry
Registry approval
An elizaOS core team member will review your registry pull request to ensure all requirements are met, the plugin is free of malicious code, and it functions as intended with proper images and a quality description.
Typical review time: 1-3 business days
If approved: Your plugin appears in the official registry and can be discovered via elizaos plugins list
If changes requested: Address the feedback and update your plugin, then re-submit.
Step 6: Post-Publishing
Plugin is now live!
Once approved, users can install your plugin to their projects:
elizaos plugins add plugin-fal-aiFuture updates
For plugin updates after initial publishing:
The elizaos publish command is only for initial publication. For all future updates, use standard npm and Git workflows - never run elizaos publish again.
# 1. Make your changes and test locally
# 2. Update version in package.json
npm version patch # or minor/major
# 3. Build and test
bun run build
elizaos test
# 4. Publish to npm
npm publish
# 5. Push to GitHub
git add .
git commit -m "Update to version x.y.z"
git push origin mainThe elizaOS registry automatically syncs with npm updates, so you don't need to manually update the registry.