advanced60 minLesson 1 of 5

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 Development from Scratch

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

100%
PhaseDescription
LoadPlugin is discovered and loaded
InitializePlugin sets up resources
RegisterEvent handlers are registered
ActivePlugin listens for events
ShutdownCleanup and resource release

Creating a Plugin

Step 1: Initialize Package

bash
mkdir -p .opencode/plugins/code-metrics cd .opencode/plugins/code-metrics npm init -y

Step 2: Create Plugin Entry

Create src/index.ts:

typescript
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:

json
{ "plugins": { "code-metrics": { "path": ".opencode/plugins/code-metrics", "enabled": true } } }

Plugin API Surface

Available Events

EventParametersReturn
session.startsessionIdvoid
session.endsessionIdvoid
file.readpath, contentcontent
file.writepath, contentcontent
file.editpath, old, newnew
tool.execute.beforetool, argsargs
tool.execute.aftertool, args, resultresult
agent.routerequest, agentagent

Context Methods

typescript
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

typescript
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

typescript
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

bash
npm test

Best Practices

PracticeReason
Single responsibilityOne plugin, one purpose
Error handlingGraceful degradation
PerformanceDon't block main thread
LoggingUseful debug information
ConfigurationMake behavior adjustable

Practice Questions

Practice Question

What is the first phase in the plugin lifecycle?

Practice Question

Which event fires before a file is written?

Practice Question

Where should plugins be registered?

Practice Question

What should a plugin do during shutdown?

Practice Question

How do plugins access configuration?


Success

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
Progress20%