Plugin Development from Scratch
Build custom OpenCode plugins from scratch. Learn the plugin architecture, lifecycle events, API surface, and how to create plugins that extend OpenCode's capabilities.
Plugin Architecture
Plugins are Node.js packages that extend OpenCode's functionality:
.opencode/plugins/my-plugin/
├── package.json
├── src/
│ └── index.ts
├── assets/
├── examples/
├── references/
└── scripts/
Plugin Lifecycle
Phase | Description |
|---|---|
Load | Plugin is discovered and loaded |
Initialize | Plugin sets up resources |
Register | Event handlers are registered |
Active | Plugin listens for events |
Shutdown | Cleanup and resource release |
Creating a Plugin
Step 1: Initialize Package
mkdir -p .opencode/plugins/code-metrics
cd .opencode/plugins/code-metrics
npm init -yStep 2: Create Plugin Entry
Create src/index.ts:
import { Plugin, PluginContext } from "opencode";
export default class CodeMetricsPlugin implements Plugin {
name = "code-metrics";
version = "1.0.0";
async initialize(context: PluginContext) {
console.log("Code Metrics plugin initialized");
}
async onFileWrite(filePath: string, content: string) {
const lines = content.split("\n").length;
const bytes = Buffer.byteLength(content);
console.log(`File written: ${filePath}`);
console.log(` Lines: ${lines}`);
console.log(` Bytes: ${bytes}`);
return { lines, bytes };
}
async onToolExecute(tool: string, args: any, result: any) {
// Log tool usage metrics
return {
tool,
timestamp: Date.now(),
success: !result.error
};
}
async shutdown() {
console.log("Code Metrics plugin shutting down");
}
}Step 3: Register Plugin
Add to opencode.json:
{
"plugins": {
"code-metrics": {
"path": ".opencode/plugins/code-metrics",
"enabled": true
}
}
}Plugin API Surface
Available Events
Event | Parameters | Return |
|---|---|---|
session.start | sessionId | void |
session.end | sessionId | void |
file.read | path, content | content |
file.write | path, content | content |
file.edit | path, old, new | new |
tool.execute.before | tool, args | args |
tool.execute.after | tool, args, result | result |
agent.route | request, agent | agent |
Context Methods
context.log(message: string, level: "info" | "warn" | "error");
context.getConfig(key: string): any;
context.setConfig(key: string, value: any): void;
context.getMemory(key: string): any;
context.setMemory(key: string, value: any): void;Example: Security Scanner Plugin
import { Plugin, PluginContext } from "opencode";
const DANGEROUS_PATTERNS = [
/eval\s*\(/,
/new\s+Function\s*\(/,
/process\.exit/,
/require\s*\(\s*['"]child_process['"]\s*\)/,
];
export default class SecurityScannerPlugin implements Plugin {
name = "security-scanner";
version = "1.0.0";
async onFileWrite(path: string, content: string) {
const warnings: string[] = [];
for (const pattern of DANGEROUS_PATTERNS) {
if (pattern.test(content)) {
warnings.push(`Potentially dangerous pattern: ${pattern.source}`);
}
}
if (warnings.length > 0) {
console.warn(`Security warnings for ${path}:`);
warnings.forEach(w => console.warn(` - ${w}`));
}
return { warnings };
}
}Testing Plugins
Unit Tests
import CodeMetricsPlugin from "../src/index";
describe("CodeMetricsPlugin", () => {
let plugin: CodeMetricsPlugin;
beforeEach(() => {
plugin = new CodeMetricsPlugin();
});
it("should count lines correctly", async () => {
const result = await plugin.onFileWrite("test.ts", "line1\nline2\nline3");
expect(result.lines).toBe(3);
});
it("should count bytes correctly", async () => {
const result = await plugin.onFileWrite("test.ts", "hello");
expect(result.bytes).toBe(5);
});
});Integration Tests
npm testBest Practices
Practice | Reason |
|---|---|
Single responsibility | One plugin, one purpose |
Error handling | Graceful degradation |
Performance | Don't block main thread |
Logging | Useful debug information |
Configuration | Make behavior adjustable |
Practice Questions
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What is the first phase in the plugin lifecycle?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which event fires before a file is written?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Where should plugins be registered?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
What should a plugin do during shutdown?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
How do plugins access configuration?
Key Takeaways
- Plugins are Node.js packages that extend OpenCode's functionality
- The lifecycle includes Load, Initialize, Register, Active, and Shutdown phases
- Use the file.write event to validate or transform content before saving
- Register plugins in opencode.json under the plugins key
- Follow single responsibility principle for plugin design
- Always cleanup resources during shutdown
- Test plugins with both unit and integration tests