# Templates Starter files transcribed from the OpenCode V2 doc pages. **Copy one and fill in the blanks** rather than writing a plugin from scratch. | Template | Use for | Source doc | | -------- | ------- | ---------- | | `promise-plugin.ts` | Server plugin, Promise flavor (default) | | | `effect-plugin.ts` | Server plugin, Effect-native | | | `rpc.ts` | RPC contract, Promise flavor (copy to `src/rpc.ts`) | | | `rpc-implementation.ts` | RPC registration + calls, Promise | | | `rpc-effect.ts` | RPC contract, Effect flavor | | | `rpc-implementation-effect.ts` | RPC registration + calls, Effect | | | `tui-plugin.tsx` | TUI / CLI plugin | | | `opencode-plugin-config.jsonc` | `plugins` in `opencode.json(c)` | | | `package.json` | Publishing a plugin package (strict JSON) | | | `package-json.md` | Publishing guidance, peer deps, entrypoints | build/plugins, build/plugins/effect, build/plugins/cli | | `README.md` | This index | — | `package.json` is strict JSON with no comments, because Node, bun, npm, and bundlers parse it with a strict parser. Publishing guidance therefore lives in `package-json.md` rather than inside the manifest. The two RPC template pairs are **not** interchangeable: same contract shape, different schema dialect, handler signature, and registration entrypoint. See the Promise-vs-Effect table in `../references/rpc.md`. ## Before you write code 1. Check the version: `opencode --version`, `opencode api get /api/info`. 2. Pick the flavor. Default to Promise. Effect only if the project already uses Effect. TUI is a separate export. RPC is a contract plus a registration. 3. Open the source doc in the table above and keep it open. 4. Copy the template. Delete the sections you do not need. 5. Verify every field name against that page. Do not guess. ## Rules the templates encode These are the constraints the pages state, not stylistic preferences. Note which are type-level rather than documented runtime failures. - A `transform` callback is typed `(editor) => void` in both flavors — no Promise, no Effect. - Tool `update` preserves name and namespace. **Assign** a new schema or options object to replace one rather than mutating nested values. In-place edits inside an `update` callback are fine — that is what the mutable draft is for. - `reload()` replays transforms. It does **not** rerun `setup` / `effect`. - Captured inputs are not watched. Load external data before the sync callback, then call `reload()` when it changes. - Use the **effective** tool name — including its namespace — for `update` and `remove` (`example_greeting`, not `greeting`). Only `add` creates a tool. - A `context` hook covers the agent loop only. Register each request kind you actually need. - Result shape is **per operation, not per flavor**: `storage.get` returns a value, `model.list()` is enveloped. Copy the page's example for the call you use. - Effect plugins use `effect`, not `setup`. The two flavors do not have the same context members or the same hook set. - Relative plugin paths in config resolve from the **config file**, not the working directory. - Registering an event subscription without tearing it down leaks it. `forkScoped` handles this in Effect; `AbortController` in Promise. - `package.json` must be strict JSON — `//` comments there break `bun`, `npm`, and every bundler. `opencode.jsonc` may use them. ## Anything not in a template The pages carry more detail than fits here. When you need something not in these files, go to the source doc rather than improvising: - Every `Agent.Info`, `Provider.Info`, `Model.Info`, `Session.Info`, `Tool.Content`, `Mcp.Server`, `Skill.Info`, `Vcs.Info`, `Reference.Info`, `Integration.Info`, `Connection.Info`, `Form.Answer`, `Command.Info`, `Plugin.Info`, `WebSearch.*`, `Worktree*` shape: - The exact contract of the running server: `/openapi.json` on that server. - Hook event fields: the `Reference` code block at the end of each section on the relevant page.