If you're working with OpenCode - the AI coding agent built for the terminal - you might be wondering: "Can I hook into OpenCode's behavior? Can I automate tasks or extend its functionality?"
The short answer: Yes, absolutely! OpenCode has a robust, native hook system that many developers don't know about yet.
What is OpenCode?
Before we dive into hooks, let's quickly cover what OpenCode is. OpenCode is an AI coding assistant that runs in your terminal, similar to Claude Code or GitHub Copilot, but with a key difference: it's open-source and highly extensible. It helps you write code, debug issues, refactor projects, and automate development tasks using AI.
Why Would You Need Hooks?
Imagine you want to:
- 🔒 Prevent OpenCode from reading sensitive files like
.env
- ✅ Automatically format code after OpenCode edits it
- 📢 Send notifications to Slack when a coding session completes
- 🔍 Log all AI interactions for compliance or analysis
- 🚀 Trigger deployments after successful code changes
- 🧪 Run tests automatically when files are modified
All of this is possible with OpenCode's hook system. Let me show you how.
Six Ways to Implement Hooks in OpenCode
OpenCode provides six different mechanisms for hooks and extensibility. You can choose the one that fits your use case:
1. Plugin System (Most Powerful)
The plugin system is your main tool for implementing hooks. Plugins are JavaScript or TypeScript files that can intercept events and tool executions.
When to use: Complex logic, event handling, custom validations
Example - Protect sensitive files:
import type { Plugin } from "@opencode-ai/plugin"
export const EnvProtection: Plugin = async ({ client }) => {
return {
tool: {
execute: {
before: async (input, output) => {
// This runs BEFORE any tool executes
if (input.tool === "read" &&
output.args.filePath.includes(".env")) {
throw new Error("🚫 Cannot read .env files")
}
}
}
}
}
}
Save this as .opencode/plugin/env-protection.ts
and it automatically prevents OpenCode from reading environment files!
Example - Auto-format code after edits:
export const AutoFormat: Plugin = async ({ $ }) => {
return {
tool: {
execute: {
after: async (input, output) => {
// This runs AFTER the tool completes
if (input.tool === "edit") {
await $`prettier --write ${output.args.filePath}`
console.log("✨ Code formatted!")
}
}
}
}
}
}
2. SDK with Event Streaming
The SDK gives you programmatic control over OpenCode from external applications. It uses Server-Sent Events (SSE) to stream what's happening in real-time.
When to use: External integrations, monitoring, CI/CD automation
Example - Monitor sessions and send notifications:
import { createOpencodeClient } from "@opencode-ai/sdk"
const client = createOpencodeClient({
baseUrl: "http://localhost:4096"
})
// Subscribe to all events
const eventStream = await client.event.subscribe()
for await (const event of eventStream) {
console.log("📡 Event:", event.type)
if (event.type === "session.idle") {
// Session completed - send notification
await sendSlackMessage("OpenCode session completed!")
}
}
Start the OpenCode server: opencode serve --port 4096
Install SDK: npm install @opencode-ai/sdk
3. MCP Servers (Model Context Protocol)
MCP servers let you add external tools and capabilities that OpenCode can use during coding sessions.
When to use: Adding new tools, integrating third-party services
Example configuration in opencode.json
:
{
"mcp": {
"database-query": {
"type": "local",
"command": ["node", "./mcp-servers/database.js"],
"enabled": true
},
"company-docs": {
"type": "remote",
"url": "https://docs.mycompany.com/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
Real-world MCP servers you can use:
- Appwrite - Backend services
- Context7 - Documentation search
- Bright Data - Web scraping
- Playwright - Browser automation
4. GitHub Integration
The GitHub integration works like webhooks for your repositories. OpenCode automatically responds to comments in issues and PRs.
When to use: PR automation, issue triage, code review
Setup:
opencode github install
Usage in GitHub:
- Comment
/opencode explain this issue
on any issue - Comment
/opencode fix this bug
to create a branch and PR - Comment
/opencode review these changes
on a PR
Manual workflow configuration:
name: opencode
on:
issue_comment:
types: [created]
jobs:
opencode:
if: contains(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sst/opencode/github@latest
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
model: anthropic/claude-sonnet-4-20250514
5. Custom Commands
Custom commands are reusable prompts saved as Markdown files. They can execute shell commands and accept arguments.
When to use: Common tasks, templates, simple automation
Example .opencode/command/test.md
:
---
description: "Run tests with coverage"
---
Run the full test suite with coverage:
`!npm test -- --coverage`
Analyze the results and suggest improvements for:
- Test coverage gaps
- Slow tests
- Flaky tests
Usage: Type /test
in OpenCode to run this command.
With arguments:
---
description: Test a specific component
---
Run tests for the $COMPONENT component:
`!npm test -- $COMPONENT.test.ts`
Review the results and suggest fixes.
Usage: /test Button
automatically substitutes $COMPONENT
with Button
6. Non-Interactive Mode
The non-interactive mode lets you script OpenCode for automation without the TUI interface.
When to use: CI/CD pipelines, pre-commit hooks, batch processing
Examples:
# Run a command and get JSON output
opencode run "analyze code quality" -f json -q
# Continue a previous session
opencode run "implement the fixes" -c session-id
# Use in a pre-commit hook
opencode run "review my changes and ensure no secrets are committed" -q
Complete Comparison Table
Mechanism | Complexity | Flexibility | Best For |
---|---|---|---|
Plugin System | High | Very High | Custom logic, event hooks, validations |
SDK/API | Medium | Very High | Full programmatic control, integrations |
MCP Servers | Medium | High | External tools, third-party protocols |
GitHub Integration | Low | Medium | PR/issue workflows, repository automation |
Custom Commands | Low | Low | Reusable prompts, simple automation |
Non-Interactive | Low | Medium | CI/CD, scripts, batch processing |
Practical Use Cases with Code
Security: Prevent Reading Sensitive Files
export const SecurityPlugin: Plugin = async ({ client }) => {
const sensitivePatterns = ['.env', 'secret', 'credentials', 'private-key']
return {
tool: {
execute: {
before: async (input, output) => {
if (input.tool === "read") {
const filePath = output.args.filePath.toLowerCase()
if (sensitivePatterns.some(pattern => filePath.includes(pattern))) {
throw new Error(`🚫 Blocked: Cannot read sensitive file ${output.args.filePath}`)
}
}
}
}
}
}
}
Quality: Auto-format and Test After Edits
export const QualityPlugin: Plugin = async ({ $ }) => {
return {
tool: {
execute: {
after: async (input, output) => {
if (input.tool === "edit") {
const file = output.args.filePath
// Format the file
await $`prettier --write ${file}`
console.log("✨ Formatted:", file)
// Run tests
const result = await $`npm test ${file}`.quiet()
if (result.exitCode !== 0) {
console.warn("⚠️ Tests failed for:", file)
}
}
}
}
}
}
}
Notifications: Slack Integration
export const SlackNotifier: Plugin = async () => {
return {
event: async ({ event }) => {
if (event.type === "session.idle") {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `✅ OpenCode session completed!`,
blocks: [{
type: "section",
text: {
type: "mrkdwn",
text: `*Session ID:* ${event.properties.sessionId}\n*Files modified:* ${event.properties.filesModified}`
}
}]
})
})
}
}
}
}
Automated Code Review with SDK
import { createOpencodeClient } from "@opencode-ai/sdk"
async function autoReview() {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096"
})
// Create a new session
const session = await client.session.create({
title: "Automated Code Review"
})
// Get modified files
const files = await client.file.status()
const modifiedFiles = files.filter(f => f.status === "modified")
// Review each file
for (const file of modifiedFiles) {
const content = await client.file.read({ path: file.path })
await client.session.chat({
id: session.id,
providerID: "anthropic",
modelID: "claude-sonnet-4-20250514",
parts: [{
type: "text",
text: `Review this file for:
- Code quality issues
- Security vulnerabilities
- Performance problems
- Best practice violations
File: ${file.path}
\`\`\`
${content}
\`\`\``
}]
})
}
// Share the review
const shared = await client.session.share({ id: session.id })
console.log("📊 Review URL:", shared.shareUrl)
}
Getting Started: Step-by-Step
1. Install OpenCode
npm install -g @opencode-ai/cli
2. Create Your First Plugin
# Create plugin directory
mkdir -p .opencode/plugin
# Install plugin types
npm install @opencode-ai/plugin
Create .opencode/plugin/my-first-plugin.ts
:
import type { Plugin } from "@opencode-ai/plugin"
export const MyFirstPlugin: Plugin = async ({ app, client, $ }) => {
console.log("🎉 Plugin loaded!")
return {
event: async ({ event }) => {
console.log("📡 Event:", event.type)
}
}
}
3. Enable Your Plugin
Create/edit opencode.json
:
{
"$schema": "https://opencode.ai/config.json",
"plugins": {
"my-first-plugin": {
"enabled": true
}
}
}
4. Test It
opencode
# You should see: 🎉 Plugin loaded!
Configuration Priority
OpenCode looks for configuration in this order:
-
OPENCODE_CONFIG
environment variable -
./opencode.json
(project directory) -
~/.config/opencode/opencode.json
(global)
Community Demand and Real-World Usage
The OpenCode community actively requested hooks before the current system was fully documented:
- Issue #1473 "Hooks support?" - Developer wanted to automate commits and PRs after tasks
- Issue #753 "Extensible Plugin System" - Proposed complete architecture with lifecycle, conversation, and tool call hooks
- Issue #2185 "Hooks for commands" - Requested command-level hooks for pre/post LLM processing
Community integrations:
- opencode.nvim - Forwards OpenCode events as Neovim autocmds
- opencode-mcp-tool - MCP server to control OpenCode from other systems
- Context7 MCP - Documentation search integration
- Bright Data Web MCP - Advanced web scraping
Developers on X/Twitter share custom hook implementations regularly. Articles on Medium and DEV Community rank OpenCode above Claude Code and Aider specifically for plugin and hook flexibility.
What OpenCode Doesn't Have
For completeness, OpenCode does NOT have:
- ❌ Traditional HTTP webhooks (POST endpoints)
- ❌ Direct git hooks (pre-commit, post-commit)
- ❌ Webhook receiver endpoints for external services
But the event-driven internal hooks cover the same use cases in a more integrated way.
Documentation Resources
- Plugins: https://opencode.ai/docs/plugins/
- SDK: https://opencode.ai/docs/sdk/
- MCP Servers: https://opencode.ai/docs/mcp-servers/
- GitHub Integration: https://opencode.ai/docs/github/
- Commands: https://opencode.ai/docs/commands/
- Configuration: https://opencode.ai/docs/config/
- GitHub Repository: https://github.com/sst/opencode
Conclusion
OpenCode has a mature, production-ready hook system that many developers don't know about yet. Whether you need simple automation with custom commands or sophisticated event-driven workflows with plugins and the SDK, OpenCode has you covered.
Start simple:
- Use custom commands for repetitive tasks
- Add plugins when you need custom logic
- Use the SDK for external integrations
- Add MCP servers for new capabilities
- Enable GitHub integration for repository automation
The architecture allows you to combine multiple mechanisms as needed, offering exceptional flexibility without requiring any workarounds.
Have you built something cool with OpenCode hooks? Share your experience in the comments! 👇
Found this helpful? Give it a ❤️ and follow!
Top comments (0)