OpenCode: Config, TUI, Themes, Keybindings, Modes & Plugins

Configuration: opencode.json

Location: ~/.config/opencode/opencode.json (global) + <repo>/opencode.json (project)

Supports JSON with comments (.jsonc). Add "$schema": "https://opencode.ai/config.json" for IDE validation.

Core Structure

{
  "$schema": "https://opencode.ai/config.json",
  "provider": { },       // LLM provider configurations
  "agent": { },          // Custom agent definitions
  "mcp": { },            // MCP server connections
  "permission": { },     // Tool allow/deny/ask rules
  "instructions": [ ],   // Additional instruction file paths
  "snapshot": true,       // Enable file change tracking for undo/redo
  "share": {              // Session sharing configuration
    "provider": "opencode"
  }
}

Provider Configuration Pattern

Each provider follows a common structure:

{
  "provider": {
    "<provider-name>": {
      "id": "provider-id",
      "api": {
        "apiKey": "{env:PROVIDER_API_KEY}",
        "baseURL": "https://api.provider.com/v1"
      },
      "models": {
        "<model-alias>": {
          "id": "model-id",
          "name": "Display Name",
          "attachment": true,       // Supports file attachments
          "reasoning": true,        // Extended thinking capable
          "temperature": 0,
          "context_length": 200000
        }
      }
    }
  }
}

Permissions

{
  "permission": {
    "allow": [
      "bash(git *)",
      "bash(make *)",
      "read(**/*.adoc)",
      "edit(**/*.adoc)"
    ],
    "deny": [
      "bash(rm -rf *)",
      "read(.env*)",
      "read(~/.secrets/*)"
    ],
    "ask": [
      "bash(docker *)"
    ]
  }
}

Permission modes:

  • "allow" — Always permitted, no confirmation

  • "deny" — Always blocked

  • "ask" — Prompt user for confirmation

Supports glob patterns for file paths and command prefixes.

Instructions

Additional instruction files beyond AGENTS.md:

{
  "instructions": [
    ".opencode/context/*.md",
    "~/.config/opencode/global-rules.md"
  ]
}

Glob patterns supported. Files are injected into the system prompt alongside AGENTS.md.

Snapshot System

{
  "snapshot": true
}

When enabled (default), OpenCode tracks all file changes for /undo and /redo support. Creates snapshots before each file modification, enabling safe rollback of any edit.

Configuration: tui.json

Location: ~/.config/opencode/tui.json

Controls the terminal UI appearance and behavior. Schema: opencode.ai/tui.json

Core Settings

{
  "$schema": "https://opencode.ai/tui.json",
  "theme": "catppuccin",
  "keybinds": { },
  "scroll": {
    "speed": 3,
    "page_fraction": 0.8
  },
  "diff": {
    "style": "unified"
  }
}

Theme Selection

{
  "theme": "catppuccin"
}

Built-in themes:

Theme Style

system

Adapts to terminal colors (default)

catppuccin

Catppuccin Latte/Mocha (our pick)

catppuccin-macchiato

Catppuccin Macchiato variant

tokyonight

Tokyo Night

everforest

Everforest

ayu

Ayu

gruvbox

Gruvbox

kanagawa

Kanagawa

nord

Nord

matrix

Green-on-black terminal aesthetic

one-dark

Atom One Dark

Custom themes can be added via .opencode/themes/ or ~/.config/opencode/themes/.

Scroll Behavior

{
  "scroll": {
    "speed": 3,
    "page_fraction": 0.8
  }
}
  • speed — Lines per scroll event

  • page_fraction — Fraction of viewport for page up/down

Diff Style

{
  "diff": {
    "style": "unified"
  }
}

Controls how file diffs are displayed in the TUI.

Themes

OpenCode ships with 11+ built-in themes and supports fully custom themes via JSON files.

Active Theme

Catppuccin Mocha — Consistent with the rest of the Domus ecosystem (Neovim, Hyprland, Waybar, tmux, Antora UI).

// tui.json
{
  "theme": "catppuccin"
}
Theme Description Palette

system

Adapts to terminal colors

Terminal default

catppuccin

Catppuccin Latte/Mocha (auto dark/light)

Pastel warm

catppuccin-macchiato

Catppuccin Macchiato variant

Pastel cool

tokyonight

Tokyo Night

Blue/purple

everforest

Everforest

Green/earth

ayu

Ayu

Orange/amber

gruvbox

Gruvbox

Warm retro

kanagawa

Kanagawa (inspired by The Great Wave)

Blue/cream

nord

Nord

Arctic blue

matrix

Green-on-black terminal aesthetic

Green monochrome

one-dark

Atom One Dark

Muted rainbow

Custom Theme Creation

Location: .opencode/themes/ or ~/.config/opencode/themes/

// ~/.config/opencode/themes/catppuccin-domus.json
{
  "name": "catppuccin-domus",
  "colors": {
    "primary": "#cba6f7",         // Mauve
    "secondary": "#a6e3a1",       // Green
    "accent": "#f5c2e7",          // Pink
    "error": "#f38ba8",           // Red
    "warning": "#fab387",         // Peach
    "success": "#a6e3a1",         // Green
    "text": "#cdd6f4",            // Text
    "background": "#1e1e2e",      // Base
    "border": "#585b70",          // Surface2
    "muted": "#6c7086",           // Overlay0
    "diff": {
      "added": "#a6e3a1",
      "removed": "#f38ba8",
      "changed": "#f9e2af"
    },
    "markdown": {
      "heading": "#cba6f7",
      "code": "#a6e3a1",
      "link": "#89b4fa",
      "bold": "#f5c2e7",
      "italic": "#f5c2e7"
    },
    "syntax": {
      "keyword": "#cba6f7",
      "string": "#a6e3a1",
      "number": "#fab387",
      "comment": "#6c7086",
      "function": "#89b4fa",
      "type": "#f9e2af",
      "variable": "#cdd6f4",
      "operator": "#89dceb"
    }
  }
}

Color Format Support

  • Hex — "#cba6f7" (standard)

  • ANSI 256 — "178" (terminal-compatible)

  • Named — "red", "blue" (basic terminals)

  • "none" — Transparent (use terminal default)

Theme Sections

Section Controls

primary / secondary / accent

UI chrome colors (borders, highlights, focus)

error / warning / success

Status message colors

text / background / border / muted

Base UI colors

diff.added / diff.removed / diff.changed

File diff visualization

markdown.*

Markdown rendering in conversation

syntax.*

Code block syntax highlighting

Dark/Light Variants

Themes can define separate palettes for dark and light terminals:

{
  "colors": {
    "dark": {
      "background": "#1e1e2e",
      "text": "#cdd6f4"
    },
    "light": {
      "background": "#eff1f5",
      "text": "#4c4f69"
    }
  }
}

Keybindings

OpenCode uses a leader key system to avoid terminal keybinding conflicts. All keybindings are fully customizable in tui.json.

Leader Key

Default: ctrl+x

The leader key acts as a prefix for multi-key sequences, similar to tmux prefix or Vim leader:

ctrl+x → n    # New session
ctrl+x → s    # Share session
ctrl+x → q    # Quit

Default Keybindings

Session Management

Keybind Action

ctrl+x → n

New session

ctrl+x → s

Share current session

ctrl+x → q

Quit OpenCode

ctrl+x → h

Session history/picker

ctrl+x → ?

Show help / keybinding reference

Model & Agent

Keybind Action

Tab

Toggle between Build and Plan mode

ctrl+x → m

Model picker (switch model)

ctrl+x → a

Agent picker (switch agent)

ctrl+x → t

Theme picker

ctrl+x → p

Provider info (active model, cost)

Input Editing (Readline/Emacs Style)

Keybind Action

ctrl+a

Move to beginning of line

ctrl+e

Move to end of line

ctrl+w

Delete word backward

ctrl+u

Delete to beginning of line

ctrl+k

Delete to end of line

ctrl+d

Delete character under cursor

alt+b

Move word backward

alt+f

Move word forward

Navigation (Vim-Inspired)

Keybind Action

j / k

Scroll down/up in message list

h / l

Collapse/expand in list views

g / G

Jump to top/bottom

Page Up / Page Down

Page scroll

Undo/Redo

Keybind Action

ctrl+z

Undo last file change

ctrl+y

Redo last undo

/undo

Undo via command (equivalent)

/redo

Redo via command (equivalent)

Custom Keybinding Configuration

// tui.json
{
  "keybinds": {
    "leader": "ctrl+x",
    "chat": {
      "submit": "ctrl+enter",
      "new_session": "ctrl+x n",
      "share": "ctrl+x s",
      "model_picker": "ctrl+x m",
      "agent_picker": "ctrl+x a",
      "toggle_mode": "tab"
    },
    "global": {
      "quit": "ctrl+x q",
      "help": "ctrl+x ?",
      "history": "ctrl+x h"
    }
  }
}

Disabling Keybindings

Set any keybind to "none" to disable:

{
  "keybinds": {
    "chat": {
      "share": "none"
    }
  }
}

Comparison with Claude Code Keybindings

Action Claude Code OpenCode

Submit prompt

Ctrl+Shift+S

Ctrl+Enter

Model picker

Ctrl+P

Ctrl+X → M

Toggle thinking

Ctrl+T

N/A (per-model config)

History search

Ctrl+R

Ctrl+X → H

External editor

Ctrl+E

N/A (inline editing)

Toggle mode

N/A

Tab

Undo

N/A

Ctrl+Z

Customizable

Partial (keybindings.json)

Full (tui.json)

Modes

Modes define tool/permission presets that agents operate under. OpenCode ships with 2 built-in modes and supports custom modes.

Built-in Modes

Mode Tool Access Use Case

Build

Full (read, write, edit, bash, etc.)

Default mode. Active coding, file modification, command execution.

Plan

Read-only (read, grep, glob, list, lsp)

Architecture review, code analysis, planning. No file modifications, no bash execution.

Mode Toggle

Press Tab in the TUI to toggle between Build and Plan modes.

The active mode appears in the TUI status bar. Mode changes take effect immediately for the current agent.

Custom Mode Definition

Location: .opencode/modes/ or ~/.config/opencode/modes/

---
description: Read-only mode for AsciiDoc documentation review
tools:
  allow:
    - read
    - grep
    - glob
    - list
    - skill
  deny:
    - write
    - edit
    - bash
    - apply_patch
---

# Review Mode

You are in review mode. You can read, search, and analyze files but cannot modify anything.

Focus on:
- Convention violations
- Missing attributes
- Broken cross-references
- Structural issues

Report findings as a numbered list with file:line references.

Mode Properties

Property Description

description

Shown in mode picker

tools.allow

Tools available in this mode

tools.deny

Tools blocked in this mode

Markdown body

Additional system prompt when mode is active

Planned Custom Modes

Mode Purpose Tool Access

Review

Documentation review and audit — read-only with reporting focus

Read-only

Secure

Paranoid mode — no bash, no write, no network

Read + grep + glob only

Local-Only

Force local model usage — no cloud API calls

Full tools, Ollama models only

Cost-Aware

Prefer cheapest model per task, warn before expensive operations

Full tools, model routing logic

Mode vs Agent

Concept Mode Agent

Scope

Tool restrictions and system prompt modifier

Complete behavior definition with model, tools, and persona

Toggle

Tab key or /mode command

@agent-name or agent picker

Persistence

Session-wide until toggled

Per-invocation (subagents) or session-wide (primary)

Custom

.opencode/modes/

.opencode/agents/

Example

"Review mode: read-only analysis"

"adoc-linter: AsciiDoc convention checker using Ollama"

Plugin System

OpenCode’s plugin system uses JavaScript/TypeScript modules with a rich event hook architecture. This is a significant upgrade from Claude Code’s bash-only hook system.

Plugin Architecture

Plugins are loaded from:

  • .opencode/plugins/ — Project-scoped

  • ~/.config/opencode/plugins/ — Global

  • npm packages referenced in config

Plugins are automatically installed via Bun at startup.

Plugin File Structure (Actual API)

// .opencode/plugins/asciidoc-validator.ts
export const AsciidocValidator = async ({ project, client, $, directory, worktree }) => {
  // Context parameters:
  // project  -- current project information
  // client   -- OpenCode SDK client (for logging, etc.)
  // $        -- Bun's shell API for running commands
  // directory -- working directory
  // worktree -- git worktree path

  return {
    // Hook: After file is edited
    "tool.execute.after": async (event) => {
      if (event.input.tool === "edit" &&
          event.output.args.filePath?.endsWith(".adoc")) {
        // Validate attributes against antora.yml
        client.app.log("info", "Validating AsciiDoc attributes...")
      }
    },

    // Hook: Before tool execution
    "tool.execute.before": async (event) => {
      if (event.input.tool === "bash" &&
          event.output.args.command?.includes("rm -rf")) {
        client.app.log("warn", "Blocked destructive operation")
        // Return deny to block
      }
    },
  }
};

npm Plugin Packages

{
  "plugin": [
    "opencode-helicone-session",
    "opencode-wakatime",
    "@my-org/custom-plugin"
  ]
}

npm plugins install automatically via Bun to ~/.cache/opencode/node_modules/.

Plugin Dependencies

// .opencode/package.json
{
  "dependencies": {
    "shescape": "^2.1.0"
  }
}

OpenCode runs bun install automatically before loading plugins.

Logging

client.app.log("debug", "Detailed info")
client.app.log("info", "General info")
client.app.log("warn", "Warning message")
client.app.log("error", "Error occurred")

Event Hooks (30+ Events)

Organized by category from the actual OpenCode plugin API:

Command Events

Event When

command.executed

A slash command was executed

File Events

Event When

file.edited

A file was modified (edit, write, patch)

file.watcher.updated

File watcher detected external change

Session Events

Event When

session.created

New session started

session.updated

Session state changed

session.compacted

Context was compacted (summarized)

session.deleted

Session was deleted

session.diff

File diff generated

session.error

Session error occurred

session.idle

Session became idle

session.status

Session status changed

Message Events

Event When

message.updated

Message content changed

message.removed

Message was deleted

message.part.updated

Part of a message changed

message.part.removed

Part of a message removed

Tool Events

Event When

tool.execute.before

Before tool execution (can intercept)

tool.execute.after

After tool execution

Permission Events

Event When

permission.asked

User was prompted for permission

permission.replied

User responded to permission prompt

Other Events

Event When

server.connected

Client connected to server

todo.updated

Todo list changed

shell.env

Shell environment variable set

lsp.client.diagnostics

LSP diagnostics received

lsp.updated

LSP state changed

installation.updated

OpenCode installation updated

tui.prompt.append

Text appended to TUI input

tui.command.execute

TUI command executed

tui.toast.show

Toast notification displayed

Experimental Hooks

Hook Purpose

experimental.session.compacting

Customize what context is preserved during auto-compaction

Plugin vs Claude Code Hooks Comparison

Aspect Claude Code Hooks OpenCode Plugins

Language

Bash scripts only

JavaScript/TypeScript

Event model

5 lifecycle events

9+ event types with richer payloads

Blocking

Exit code 2 blocks action

Return deny: true with reason

Custom tools

Not possible

Plugins can define new tools

State

Stateless between invocations

Persistent module state

Dependencies

System packages

npm ecosystem (auto-installed via Bun)

Debugging

Shell output parsing

Full JS debugging, stack traces

Distribution

Copy files

npm packages or git-managed files

Planned Plugins

Plugin Purpose Priority

asciidoc-validator

Validate {attributes} against antora.yml after every .adoc edit

P1

auto-backup

Snapshot files before edit/write (mirrors Claude Code PreToolUse hook)

P1

security-guard

Block sensitive file access, warn on credential staging

P1

shellcheck-runner

Run ShellCheck on .sh/.bash/.zsh after edits

P2

session-logger

Log all tool calls and model switches for cost tracking

P2

cost-tracker

Estimate token usage and cost per provider per session

P2

Plugin Development Workflow

# Create plugin directory
mkdir -p .opencode/plugins

# Write plugin
nvim .opencode/plugins/my-plugin.ts

# OpenCode auto-discovers and loads on next session start
# No registration needed -- file existence is sufficient
Plugins have full access to the Node.js/Bun runtime. A malicious plugin could execute arbitrary code. Only install plugins you trust or have authored yourself.

Auto-Formatters

OpenCode automatically formats files after every edit, write, and apply_patch operation. This is a feature Claude Code does not have — formatters run transparently on every file modification.

Built-in Formatters (27)

Formatter Extensions Requirement

air

.R

air command

biome

.js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml

biome.json(c) config present

cargofmt

.rs

cargo fmt command

clang-format

.c, .cpp, .h, .hpp, .ino

.clang-format config present

cljfmt

.clj, .cljs, .cljc, .edn

cljfmt command

dart

.dart

dart command

dfmt

.d

dfmt command

gleam

.gleam

gleam command

gofmt

.go

gofmt command

htmlbeautifier

.erb, .html.erb

htmlbeautifier command

ktlint

.kt, .kts

ktlint command

mix

.ex, .exs, .eex, .heex

mix command

nixfmt

.nix

nixfmt command

ocamlformat

.ml, .mli

ocamlformat + .ocamlformat config

ormolu

.hs

ormolu command

oxfmt

.js, .jsx, .ts, .tsx

Experimental (env var flag required)

pint

.php

laravel/pint in composer.json

prettier

.js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml

prettier in package.json

rubocop

.rb, .rake, .gemspec, .ru

rubocop command

ruff

.py, .pyi

ruff command + config

rustfmt

.rs

rustfmt command

shfmt

.sh, .bash

shfmt command

standardrb

.rb, .rake, .gemspec, .ru

standardrb command

terraform

.tf, .tfvars

terraform command

uv

.py, .pyi

uv command

zig

.zig, .zon

zig command

Configuration

Disable All Formatters

{
  "formatter": false
}

Disable Specific Formatter

{
  "formatter": {
    "prettier": {
      "disabled": true
    }
  }
}

Custom Formatter

{
  "formatter": {
    "custom-markdown": {
      "command": ["deno", "fmt", "$FILE"],
      "extensions": [".md"]
    },
    "prettier": {
      "command": ["npx", "prettier", "--write", "$FILE"],
      "environment": { "NODE_ENV": "development" },
      "extensions": [".js", ".ts", ".jsx", ".tsx"]
    }
  }
}

The $FILE placeholder is replaced with the absolute path of the file being formatted.

Relevant Formatters for Our Stack

Formatter Our Use Case Install

shfmt

Shell scripts (.sh, .bash)

pacman -S shfmt

ruff

Python files (.py)

uv tool install ruff

rustfmt

Rust files (.rs)

Bundled with rustup

prettier

JSON, YAML, Markdown

npm install -D prettier

gofmt

Go files

Bundled with Go

Formatters run silently after every file modification. This means code is always clean without manual intervention — a significant advantage over Claude Code where formatting is manual.