A coding assistant becomes more useful when you can see what matters while you work. Perhaps you want a clearer view of tool activity, a review panel beside the conversation, or a command that performs a routine action immediately. The starting point is a specific inconvenience you encounter repeatedly.

Claude Code mods let you change parts of the assistant’s behavior and interface using JavaScript or TypeScript. The interesting question is not how many features you can add. It is which small change makes your next task easier to complete and verify.

This guide moves from that decision to a small example, then to testing and installation. Technical details were checked against Anthropic’s documentation on October 7, 2026. Because the API can evolve, compare examples with the declarations generated by your installed version.

Start with the smallest useful customization

Before writing code, state the result you want in one sentence: “Show how many tool calls this loaded mod has observed.” That gives you a visible outcome, a simple test, and a reason to remove the mod if it proves distracting.

Option

A useful starting point

Project instructions

Record repository conventions and expectations in CLAUDE.md.

Skill

Package a procedure you repeatedly ask Claude to follow.

Settings hook

Use an existing script to react to a lifecycle event, log activity, or control an action.

MCP server

Connect Claude to external tools or data, such as a service or database.

Mod

Change an event inside Claude Code or add interactive interface behavior.

These tools can work together. A plugin is the distribution package; it can contain skills, hooks, MCP configuration, and mod code. Installing a plugin does not necessarily mean you have installed a mod.

Compare customization options in the official mods overview

Understand the event before changing it

A mod registers handlers for events. When a matching event occurs, Claude Code supplies the mods API, the event data, and a next function. That function continues through the remaining handlers toward the normal behavior.

  • Observe: record something useful, then return next(event).

  • Rewrite: pass a changed copy of the event to next.

  • Handle: return the event’s supported result yourself without continuing the chain.

A Claude Code event enters a mod. The handler can observe and continue, rewrite and continue, or answer without running downstream behavior.

Order matters when multiple mods handle the same event. The first handler surrounds later handlers: it sees the incoming event first and the returning result last. Managed priority groups and plugin dependencies also affect that order. Test combinations together rather than assuming each mod behaves exactly as it does alone.

How events, continuation, and mod ordering work

Build a small TypeScript mod

Our example adds an observed-call counter to the spinner while Claude works. It forwards tool calls unchanged and makes no model requests of its own. The counter measures calls reaching this handler, including attempts that might later fail or be denied. It does not measure successful operations, tokens, or cost.

Create the plugin files

Use a directory named workflow-counter with this layout. Claude Code loads TypeScript directly; this example needs no bundler.

workflow-counter/
  .claude-plugin/
    plugin.json
  hooks/
    hooks.json
    register.ts

Save the manifest as .claude-plugin/plugin.json:

{
  "name": "workflow-counter",
  "version": "0.1.0",
  "description": "Shows the tool calls observed by this loaded mod"
}

Save hooks/hooks.json. The module path is relative to this configuration file:

{
  "modules": [
    "./register.ts"
  ]
}

Save the following as hooks/register.ts. The Register type comes from Claude Code’s mod declarations; it is not a package you need to install from npm.

import type { Register } from 'claude-code';

export const register: Register = (on) => {
  let observed = 0;

  on('tool.call', async ($, event, next) => {
    observed += 1;
    $.ui.invalidate('ui.render');
    return next(event);
  });

  on('ui.render', { component: 'Spinner' }, async ($, event, next) => {
    const previous = event.props.suffix ?? '';
    return next({
      ...event,
      props: {
        ...event.props,
        suffix: previous + ' | observed calls: ' + observed,
      },
    });
  });
};

The counter lives inside register, so it starts at zero when the mod is registered again. The tool handler increments it and requests a redraw; the interface handler appends the value while preserving an existing suffix. A later mod can still replace that suffix, which is one reason to check the complete configuration.

File layout and event contracts in the mods reference

Load, inspect, and improve the example

claude --version
claude plugin validate ./workflow-counter
claude --plugin-dir ./workflow-counter

During development, --plugin-dir loads the directory for that session and watches changes. Loading it also generates declarations under .claude-plugin/types/ and a suitable root TypeScript configuration when needed. Use those files to check the exact API your build supports.

Ask Claude to read a harmless file in a test workspace. Watch the spinner count increase during activity. Change the label and save the module to check reload behavior; this example’s count resets when it registers again. An idle spinner is not a persistent dashboard.

You can also ask Claude to build a mod from a precise description using its plugin-authoring skill. Review the generated files and understand what will run before accepting the session’s hot-reload prompt.

Official creation guide and generated type definitions

Test behavior, not just syntax

Validation identifies hooks and API calls and catches unsupported structures. It does not establish that a mod is safe or that its output is useful. For automated behavior checks, write .test.ts files using claude-code/testing and run claude plugin test from the plugin directory. The test kit can stub tools and model calls without running them.

  • Fire two stubbed tool events and verify the displayed count is two.

  • Check that the original event is forwarded and the existing spinner suffix is retained.

  • Reload the mod and verify that the counter restarts at zero.

  • Run alongside your other mods and inspect the combined interface.

  • Try a session without graphical mod output and confirm the tool workflow still works.

For a guard or redactor, add failure cases before relying on it: rejected commands, unexpected input, timeouts, and cancellation. A feature that looks correct in one demonstration may still behave poorly at those boundaries.

Automated testing, stubs, and interface checks

Know where the interface appears

Mod execution and visible UI are separate capabilities. A counter attached to the spinner requires a surface that renders it. Check the target environment before interpreting a missing label as a failed installation.

Environment

Mod behavior / visible UI

Terminal CLI

Handlers run; mod interface elements are supported.

Desktop Code tab, local session

Handlers and UI are supported; some elements remain terminal-only.

VS Code chat panel

Handlers can run; custom mod UI does not appear.

claude -p / Agent SDK

Handlers can run; custom mod UI does not appear.

Desktop WSL session

Plugins are unavailable, so mods do not run.

Current documentation specifies terminal Claude Code v2.1.287 or later and Desktop’s bundled Claude Code v2.1.286 or later. Check the terminal with claude --version; in Desktop, use /status inside the Code session. The old CLAUDE_CODE_ENABLE_FUNCTION_HOOKS preview variable is ignored by current terminal releases.

Runtime compatibility and current version requirements

Install once the local version earns its place

Keep developing against your own directory. For regular installation, distribute the mod through a plugin marketplace. The names below are placeholders: replace them with an actual reviewed repository, plugin name, and marketplace name.

claude plugin marketplace add <owner>/<repository>
claude plugin install <plugin-name>@<marketplace-name>

If you install from the shell while Claude Code is open, run /reload-plugins in that session. Use /plugin to check the loaded plugin and its mod entry. To remove one customization, disable its plugin rather than changing your whole configuration.

A plugin loaded from a local directory and an installed cached copy are different copies. Version the manifest when distributing updates, and document which Claude Code build you tested. This prevents an edited source file from being mistaken for an updated installation.

Marketplace installation, scopes, and plugin management

Keep control of what a mod can reach

A pleasant interface does not reveal all of a mod’s capabilities. Mods run with your account’s access and are not sandboxed. Their APIs can reach files, processes, network services, environment values, and model calls. Review the hooks and calls reported by validation alongside the source, dependencies, and update history.

For the counter above, expect only tool.call and the Spinner ui.render handler, plus $.ui.invalidate. File access, network requests, or model calls would be outside its stated purpose and deserve investigation. A simple counter should not need your API key or send session contents anywhere.

The mods API and available capabilities

For organizations, the built-in sec-default guard loads on machines with managed settings and for Team or Enterprise users. By default there, user mods cannot overturn deny rules or managed PreToolUse blocks for Claude’s tool calls. That protection does not sandbox a mod’s own file or process API calls. Administrators can restrict which mods load and must preserve the guard when configuring a custom prepend order.

Treat team policy and code review as complementary controls. Test the policy on the same authentication method and machine configuration your users actually have.

Managed protections, policy options, and their limits

A repeatable customization workflow

  1. Name the friction. Choose one observable improvement to a task you perform regularly.

  2. Choose the mechanism. Start with instructions, a skill, a settings hook, or MCP when it already meets the need.

  3. Build in a test workspace. Keep the first mod narrow and inspect its generated types.

  4. Validate and test. Confirm both expected behavior and relevant failure cases.

  5. Check the complete setup. Test other mods, the intended surface, and organization policy together.

  6. Keep or remove it. Judge whether the task became clearer and easier to verify; revisit after upgrades.

Key takeaway

A useful mod has a clear purpose, a small set of capabilities, and behavior you can explain and test. Start with visibility, make one change, and verify its effect before expanding it. Customization is valuable when it helps you understand and control the work.

If your next goal is context efficiency, measure actual usage separately. A UI counter tells you about observed tool activity; it cannot tell you how much context or money a session consumed.

Related guide: Stop Burning Tokens in Claude Code: Measure, Cut, and Keep What Matters

Resources

Anthropic announcement — Customize Claude Code with mods

Claude Code — Skills

Claude Code — Settings hooks

Claude Code — MCP integrations

Claude Code — Plugin overview