Claude Code mods: in-session JavaScript hooks for guards, readouts and review views

Anthropic's mods run JS/TS hooks inside a Claude Code session, for developers who want code, not prompts, to enforce their rules.

Picture a backend developer at a four-person agency who runs Claude Code all day against client repos. Three habits repeat every session. They remind Claude not to print anything from .env. They type /context to see why the window filled so fast. After a multi-file refactor, they scroll back through the transcript to see what changed. The reminder lives in CLAUDE.md and in chat, so it holds only as long as the model honours it. They tried a settings hook, but that's a shell script launched per event, and a script that crashes or times out can quietly let the command through.

Claude Code mods target the first part of that gap: the rule stops depending on the model. Whether a crashing mod blocks the command or lets it through, our sources don't confirm (see Where it breaks). Anthropic's getting-started post, dated October 1, 2026, describes mods as small JavaScript or TypeScript modules, shipped inside plugins, that run inside your session, see every event and can draw their own UI. As @bibryam put it, the point is "deterministic behavior without relying on prompts."

What it is

Per Anthropic's post, mods are on by default from Claude Code 2.1.287. A mod is a plugin whose hooks/hooks.json names exactly one module under modules. That module exports register(on), and on(event, matcher?, hook) adds a hook. Every hook has the same shape:

on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  // $ the mods API: ui, session, state, store, fs, process, clock, http, tool, command, model, ...
  // e this event's input, as plain data
  // next passes e to the other plugins and then to Claude Code's own behavior
  return next(e);
});

Hooks chain like middleware, and a hook can observe (call next(e), then inspect the result), rewrite (pass a modified event to next) or answer (return something like { deny: "…" } without calling next). Events cover tool calls, the submitted prompt, turns starting and finishing, session start and end, slash commands, and ui.render for each piece of the interface as it's drawn.

Here's how that maps to the lede:

Unlike a settings hook, which runs a shell command per event and passes JSON over stdin and stdout, a mod is loaded once and keeps state. It draws UI that updates as events happen, and it can call back into Claude Code to open a pane, run a process, or register a slash command or a tool the model can call. The TDS author found that each settings-hook check launched a separate process and took 55–99 milliseconds on their laptop. No source measures a mod's overhead to compare.

Anthropic builds parts of Claude Code this way: Latent Space lists six built-in mods under /plugin → Installed and says mods work in both the CLI and Desktop.

Alternatives the sources cover:

Who it's for

Pick this up if you use Claude Code daily and can name a rule you keep retyping, a number you keep checking, or a review step you keep doing by hand. You also need to read, or have Claude write, a few dozen lines of JavaScript, and test them. This is the vibe-engineering move: replace a hoped-for behaviour with a check that runs every time.

Skip it in four cases:

Before and after

Before: "never print secrets" lives in CLAUDE.md and chat, you type /context when the session feels heavy, you dig through the transcript after a refactor, and any settings hook you wrote has to never go missing or time out.

After:

  1. A guard mod on tool.call denies the commands you've named, whether or not the model remembers the rule.
  2. A readout above the prompt updates after each turn.
  3. A review command like /replay walks each edit in order (per MakeUseOf).
  4. You run claude plugin validate and, as TDS recommends, claude plugin test, and treat the mod as code under test.

Try it

Check your version first, per Anthropic:

claude --version # 2.1.287 or later

The shortest path is to describe the mod in a session. TDS prompted "Make a mod that blocks cat .env". Claude uses the built-in plugin-authoring skill and asks whether to turn on hot reloading.

That mod loads only in the session that made it. TDS says to copy its folder out of ~/.claude/dev-mods/ if you want to keep it. The DEV Community walkthrough, citing Anthropic's docs, says those folders are deleted without notice once they're older than cleanupPeriodDays, which defaults to 30 days.

To build by hand, follow Anthropic's worked example, Token Weather. The finished mod draws a one-line forecast of how full the context window is, just above the prompt, and updates it after each turn. The first step only puts fixed text there, which is enough to see how a mod is wired.

A mod is a plugin folder with three files you write:

token-weather/
├── .claude-plugin/
│   └── plugin.json
└── hooks/
    ├── hooks.json
    └── token-weather.mjs

.claude-plugin/plugin.json is a standard plugin manifest: the mod's name, version, description and author.

{
  "name": "token-weather",
  "version": "0.1.0",
  "description": "A live forecast of the context window, drawn above the prompt.",
  "author": { "name": "You" }
}

hooks/hooks.json points Claude Code at the mod's code. A mod has exactly one module:

{ "modules": ["./token-weather.mjs"] }

hooks/token-weather.mjs is the mod. The band directly above the prompt is a component called AbovePrompt. Claude Code draws nothing there itself, so it's a good first target. This hook catches that band's ui.render event and returns what to draw:

// hooks/token-weather.mjs
export function register(on) {
  on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
    const { Box, Text } = $.ui.resolve(e);
    return Box({
      paddingX: 1,
      children: [Text({ color: "yellow", bold: true, children: "☀ Clear skies" })],
    });
  });
}

Load it:

claude --plugin-dir ./token-weather

Per Anthropic, "☀ Clear skies" appears above the prompt. Keep the session open: the folder is watched, so each save reloads the module in place, with no restart. From there, Anthropic's post swaps the fixed text for a real reading. $.session.usage() returns the same context figures as the status line, the mod takes a reading at session start and after each turn, and it keeps the history in $.state so a reload doesn't wipe it. We haven't found an independent reproduction of the full Token Weather example.

To install someone else's mod, the DEV walkthrough shows /plugin install <name>@<marketplace> and then checking the Installed tab. None of the sources list a separate price for mods.

Where it breaks

Sources