Claude Code Mods: a field guide β€” build plugins that hook the engine, add commands and tools, and draw their own UI (panes, colour graphics, animation). By ruvnet. Developer ruvnet published a field guide to building Claude Code mods, plugins that hook the engine's events to block or rewrite tool calls, add slash commands and tools, and render custom UI such as status lines, panes and animated graphics. The guide documents the hooks module API (register(on, options), on('tool.call'), on('ui.render')), the sandboxed runtime with no DOM or Node access, and enabling the early-access feature via CLAUDE_CODE_ENABLE_FUNCTION_HOOKS. It also covers hot reloading through the plugin-authoring skill and the auto-generated type definitions that ship with each loaded mod. How to build Claude Code mods: plugins that hook the engine, add commands and tools, and draw their own UI inside Claude Code, from status lines to animated panes. By ruvnet https://github.com/ruvnet . Written from building and shipping a real mod, with every pitfall we hit included. Early access. Mods function hooks are an early-access Claude Code feature, and the API moves between releases. Your build writes its own type definitions next to every mod you load see Setup 2-setup , and those types are the authority. Grep them before you trust any guide, this one included. Throughout this guide: πŸ§ͺ Field-tested means we did it in a shipped mod and saw it work or break for real. πŸ“˜ Engine docs means it comes from the engine's reference and type definitions, and we haven't exercised it ourselves. A mod is a Claude Code plugin whose behaviour is a hooks module : JavaScript or TypeScript that exports register on, options . Inside it, on event, hook attaches middleware to the engine's own events. Each hook can watch an event, rewrite it, or answer it in place of the engine. | You want… | Mod mechanism | |---|---| | Block or rewrite a tool call protect .env , normalise commands | on 'tool.call', { tool }, … returning { deny } or next { ...e, … } | | React to the prompt, or rewrite it | on 'prompt.submit', … | | Add or replace a system-prompt section | on 'prompt.compose', … | | A slash command | $.command.register + on 'command.run', … | | A status-line entry | $.ui.status text | | A toast | $.ui.toast text | | A band of UI above the prompt | on 'ui.render', { component: 'AbovePrompt' }, … | | A pane beside the conversation | $.ui.open { id } + on 'ui.render', { component: 'Pane' }, … | | Draw a slash command's output as UI | on 'ui.render', { component: 'CommandOutput', props: { command } }, … | | A tool the model can call | $.tool.register + serve it in tool.call | | A subagent type | $.agent.register | | Background work | $.clock.every / $.clock.after , started in session.start | | Run programs, read files | $.process.run argv, no shell , $.fs | | Ask a model something | $.model.complete , $.model.fork | | Colour graphics and animation | Raster cells + $.ui.blit | | Mouse- and keyboard-driven widgets | Client surface modules | The module runs in a sandbox of its own: no DOM, no Node no fs , Buffer , process or node: imports . Everything outside it goes through $ . It may import only its own files and types from claude-code . Dynamic import is not allowed. Turn mods on early access : export CLAUDE CODE ENABLE FUNCTION HOOKS=1 or persistently, in ~/.claude/settings.json: "env": { "CLAUDE CODE ENABLE FUNCTION HOOKS": "1" } Load a mod pick one : claude --plugin-dir ./my-mod this session only ~/.claude/settings.json β†’ "env": { "CLAUDE CODE PLUGIN DIRS": "/abs/path/my-mod" } every session Both are watched in an interactive session: saving a file reloads the module. Hot reload while Claude writes the mod πŸ§ͺ . The built-in plugin-authoring skill watches a per-session folder, ~/.claude/dev-mods/