CCMods Hub

How to Create a Claude Code Mod

You can describe a mod and let Claude write it, or write the three files yourself. Both paths end with the same loop: load, try, validate, test.

Last checked against the official docs: 2026-10-02

Option A: ask Claude to write it

Claude Code has a built-in skill called plugin-authoring that knows which events and methods your version supports. In an interactive session, describe what you want:

make a mod that shows the current git branch above the prompt
  1. Claude writes the mod into ~/.claude/dev-mods/<session-id>/. Because ~/.claude is a protected path, you approve each file.
  2. On the first file, Claude Code asks whether to enable hot reloading. Choose Enable for this session so the mod loads when the turn ends and reloads after every change.
  3. Check it in /plugin → Installed, then try it. If it isn't right, tell Claude what to change.
A mod Claude writes only loads in the session that created it, and that folder is cleaned up eventually. To keep it, copy the folder somewhere of your own (for example ~/mods/git-branch) and load it with claude --plugin-dir ~/mods/git-branch. It won't load in claude -p, in untrusted folders, or when mods are disabled.

Option B: write it yourself

A small mod is three files:

my-mod/
├── .claude-plugin/
│   └── plugin.json      # the plugin manifest
└── hooks/
    ├── hooks.json       # points Claude Code at your code file
    └── register.js      # your code: the "hooks module"

The hooks module exports a register(on) function. Each on(event, handler) call subscribes a handler, and every handler receives ($, e, next): the mods API, the event, and a function that passes the event on. This tiny mod counts tool calls and adds the count to the spinner:

let calls = 0

export function register(on) {
  // Before every tool call: count it, then let it run unchanged
  on('tool.call', async ($, e, next) => {
    calls += 1
    $.ui.invalidate('ui.render')
    return next(e)
  })

  // When the spinner is drawn: keep it, add a suffix
  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
    return next({ ...e, props: { ...e.props, suffix: ' · tools: ' + calls } })
  })
}

Load it with claude --plugin-dir ./my-mod. Claude Code hot-reloads it whenever you save.

Three things a handler can do

Anything outside your own code (drawing, adding a command, calling a model, reading a file, running a process, making a request) goes through $: $.ui, $.command, $.model, $.fs, $.process, $.http, $.store and more. The hooks module has no Node.js APIs of its own.

Get the exact types for your version

Each time Claude Code loads a mod from --plugin-dir, it writes TypeScript declarations into .claude-plugin/types/ inside the mod folder. claude-code/index.d.ts lists every event and method in the version you are running. Events can change between releases, so trust that file over any web page, including this one.

Validate before you run

claude plugin validate ./my-mod

The output lists the events your module hooks and every $ method it calls. If an event you expected is missing, Claude Code won't call that hook either. Common causes:

Test it

Put tests in tests/ and run claude plugin test. Tests fire events at your hooks and check what they did, with no session, sign-in or network needed.

Share it

Version it in plugin.json and publish it through a plugin marketplace. Avoid names that look like Anthropic's own, such as names starting with claude-; validate rejects them. Say in your README which Claude Code version you tested with.

Next: browse existing mods for patterns to copy, or fix a mod that does nothing.

Sources: Create a mod, Mods overview, Mods API.