Follow Discord
Sweep 22 Oct 2026 · 21:04Z Build v2.1.309 528 read Stable v2.1.308 Latest v2.1.309 Next v2.1.309 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Create a mod changedplugins/mods/create

Nearest release: v2.1.304, published 8 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 20 Oct 2026 04:09 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 22 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+29added
Lines−29removed
From line 2 where the diff opens
First seen 1 Oct 2026 this site's first read of the page
Recorded edits11to this page, all time

The whole hunk

from line 2, old and new numbered
/
lines
from line 2
22 
33> Have Claude write a Claude Code mod from a description, or write one yourself that counts tool calls and adds a command. Learn the reload and validate loop.
44 
5A mod is a Claude Code [plugin](/docs/en/plugins/overview) with an entry file, called the hooks module: a JavaScript or TypeScript file whose functions Claude Code calls when events happen. To make one:
5A mod is a Claude Code [plugin](/docs/en/plugins/overview.md) with an entry file, called the hooks module: a JavaScript or TypeScript file whose functions Claude Code calls when events happen. To make one:
66 
77* **Ask Claude to write it**: [describe what you want](#ask-claude-for-a-mod) in a Claude Code session
88* **Write it yourself**: [follow the tutorial](#write-a-mod-yourself) to learn how a mod's code works. You don't need Node.js, a bundler, or a build step, because Claude Code loads `.js` and `.ts` files directly.
99 
10If you haven't decided whether a mod is the right tool, read the [comparison on the overview](/docs/en/plugins/mods/overview#compare-mods-settings-hooks-skills-and-mcp-servers) first.
10If you haven't decided whether a mod is the right tool, read the [comparison on the overview](/docs/en/plugins/mods/overview.md#compare-mods-settings-hooks-skills-and-mcp-servers) first.
1111 
1212> [!NOTE]
1313>
14> Use Claude Code v2.1.287 or later. In your shell, run `claude --version` to check. To see whether mods can load for you, see [Check whether mods can load](/docs/en/plugins/mods/troubleshoot#check-whether-mods-can-load).
14> Use Claude Code v2.1.287 or later. In your shell, run `claude --version` to check. To see whether mods can load for you, see [Check whether mods can load](/docs/en/plugins/mods/troubleshoot.md#check-whether-mods-can-load).
1515 
1616## Ask Claude for a mod
1717 
18Describe the mod you want in an interactive Claude Code session, and Claude writes it. Claude works from a built-in [skill](/docs/en/skills) named `plugin-authoring`, which tells it where to write the mod, which events and methods your version has, and how the mod gets loaded. Claude can load the skill when you ask for a mod, or you can load it yourself by running `/plugin-authoring` at the Claude Code prompt.
18Describe the mod you want in an interactive Claude Code session, and Claude writes it. Claude works from a built-in [skill](/docs/en/skills.md) named `plugin-authoring`, which tells it where to write the mod, which events and methods your version has, and how the mod gets loaded. Claude can load the skill when you ask for a mod, or you can load it yourself by running `/plugin-authoring` at the Claude Code prompt.
1919 
2020The mod runs once you approve it, except in [sessions where a mod Claude writes can't load](#sessions-that-skip-the-approval).
2121 
from line 25
2525 
2626> [!NOTE]
2727>
28> In the `default` and `acceptEdits` [permission modes](/docs/en/permission-modes), Claude Code asks you to approve each of the mod's files before Claude creates it, because the mods folder is a [protected path](/docs/en/permission-modes#protected-paths). Approve each file as it comes up.
28> In the `default` and `acceptEdits` [permission modes](/docs/en/permission-modes.md), Claude Code asks you to approve each of the mod's files before Claude creates it, because the mods folder is a [protected path](/docs/en/permission-modes.md#protected-paths). Approve each file as it comes up.
2929 
3030**2. Approve the mod**
3131 
from line 57
5757 
5858* **Nobody is there to approve**: the session can't show you a prompt, as in a `claude -p` run or [`dontAsk` mode](/docs/en/permission-modes)
5959* **The workspace isn't trusted**: you haven't accepted the trust prompt for the directory
60* **Mods are disabled**: you started with `--safe-mode` or `--bare`, you set `disableAllHooks`, or your organization's [managed settings block it](/docs/en/plugins/mods/admin#choose-how-much-to-allow)
60* **Mods are disabled**: you started with `--safe-mode` or `--bare`, you set `disableAllHooks`, or your organization's [managed settings block it](/docs/en/plugins/mods/admin.md#choose-how-much-to-allow)
6161 
6262## Write a mod yourself
6363 
from line 78
7878 └── register.js
7979```
8080 
81* **`plugin.json`**: the plugin's [manifest](/docs/en/plugins/manifest-reference)
82* **`hooks.json`**: [points to your code file](/docs/en/plugins/mods/reference#files)
81* **`plugin.json`**: the plugin's [manifest](/docs/en/plugins/manifest-reference.md)
82* **`hooks.json`**: [points to your code file](/docs/en/plugins/mods/reference.md#files)
8383* **`register.js`**: your code, called the hooks module
8484 
8585**1. Create the plugin directory**
from line 100
100100 
101101**2. Write the manifest**
102102 
103A mod is a plugin, and a mod needs a [manifest](/docs/en/plugins/manifest-reference). This mod's manifest has no special fields. Save this as `first-mod/.claude-plugin/plugin.json`:
103A mod is a plugin, and a mod needs a [manifest](/docs/en/plugins/manifest-reference.md). This mod's manifest has no special fields. Save this as `first-mod/.claude-plugin/plugin.json`:
104104 
105105```json first-mod/.claude-plugin/plugin.json theme={null}
106106{
from line 201
201201first-mod: Claude has made 0 tool calls since this mod loaded
202202```
203203 
204If `/tally` isn't in the command list, the module didn't load. See [Find out why a mod does nothing](/docs/en/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing).
204If `/tally` isn't in the command list, the module didn't load. See [Find out why a mod does nothing](/docs/en/plugins/mods/troubleshoot.md#find-out-why-a-mod-does-nothing).
205205 
206206**7. Change the code while the session runs**
207207 
from line 221
221221 
222222Each function you pass to `on` is a hook, which is an event handler. Claude Code passes every hook the same three arguments:
223223 
224* **The mods API**, named `$`: every method a mod can call to reach outside itself, in [namespaces](/docs/en/plugins/mods/reference#mods-api-methods) such as `$.ui` and `$.command`
225* **The event**, named `e`: the [event's input](/docs/en/plugins/mods/reference#events) as plain data, such as a tool call's name and arguments
224* **The mods API**, named `$`: every method a mod can call to reach outside itself, in [namespaces](/docs/en/plugins/mods/reference.md#mods-api-methods) such as `$.ui` and `$.command`
225* **The event**, named `e`: the [event's input](/docs/en/plugins/mods/reference.md#events) as plain data, such as a tool call's name and arguments
226226* **The next handler**, named [`next`](/docs/en/plugins/mods/events#how-a-hook-handles-an-event): a function that passes the event on to the other mods and then to Claude Code's own behavior, and returns the result
227227 
228228The hooks in `first-mod` handle their events in these ways:
229229 
230230* **Observe**: the `session.start` hook registers the command, and the `tool.call` hook counts the call and asks for a redraw. Both return `next(e)`, so the session starts and the tool runs as usual.
231* **Answer**: the `command.run` hook returns its own result and never calls `next`. The second argument to `on`, `{ command: 'tally' }`, is a filter, called a [matcher](/docs/en/plugins/mods/events#filter-which-events-a-hook-handles), so the hook runs only for `/tally`.
231* **Answer**: the `command.run` hook returns its own result and never calls `next`. The second argument to `on`, `{ command: 'tally' }`, is a filter, called a [matcher](/docs/en/plugins/mods/events.md#filter-which-events-a-hook-handles), so the hook runs only for `/tally`.
232232* **Rewrite**: the `ui.render` hook calls `next` with a copy of `e` whose `suffix` holds the count, so Claude Code draws its usual spinner with your text after the word
233233 
234Claude Code watches a directory loaded with `--plugin-dir` and hot-reloads the hooks module when a file in it changes. Each reload runs `register` again, so `calls` resets to `0` and `/tally` starts counting again. To keep a value across reloads, see [Keep state](/docs/en/plugins/mods/interface#keep-state).
234Claude Code watches a directory loaded with `--plugin-dir` and hot-reloads the hooks module when a file in it changes. Each reload runs `register` again, so `calls` resets to `0` and `/tally` starts counting again. To keep a value across reloads, see [Keep state](/docs/en/plugins/mods/interface.md#keep-state).
235235 
236236## Keep working on a mod
237237 
from line 245
245245claude --plugin-dir ./first-mod
246246```
247247 
248Then ask for the change, for example `add a /tally-reset command to this mod that sets the tally back to zero`. Claude edits the hooks module, runs `claude plugin validate`, and fixes what it reports. A directory you load with `--plugin-dir` is a [protected path](/docs/en/permission-modes#protected-paths), so in `default` and `acceptEdits` modes you're asked to approve each of Claude's edits to the mod. The protected paths table gives the result for the other permission modes.
248Then ask for the change, for example `add a /tally-reset command to this mod that sets the tally back to zero`. Claude edits the hooks module, runs `claude plugin validate`, and fixes what it reports. A directory you load with `--plugin-dir` is a [protected path](/docs/en/permission-modes.md#protected-paths), so in `default` and `acceptEdits` modes you're asked to approve each of Claude's edits to the mod. The protected paths table gives the result for the other permission modes.
249249 
250250Files Claude saves during its turn reload when the turn ends, so you can try `/tally-reset` as soon as Claude finishes.
251251 
from line 295
295295* Inside `register`, don't declare a second variable or parameter named `on`. Validation fails with `"on" is declared again (shadowed)`.
296296* Import only from files inside the plugin directory, by relative path. The one bare import allowed is `claude-code`, for types and a few helpers.
297297* Use `import` declarations at the top of the file, as in `import { name } from './file.js'`. A dynamic `import()` fails with `a dynamic import(); a hooks module imports its own files with an import declaration`.
298* Write every file as an ES module, with `import` and not `require`. The [reference](/docs/en/plugins/mods/reference#files) lists the file extensions Claude Code loads.
298* Write every file as an ES module, with `import` and not `require`. The [reference](/docs/en/plugins/mods/reference.md#files) lists the file extensions Claude Code loads.
299299 
300300### Test the mod
301301 
from line 337
337337Ran 1 test across 1 file. [0.19s]
338338```
339339 
340[Test a mod](/docs/en/plugins/mods/test) covers stubbing a model call or the store, and testing timers and drawings.
340[Test a mod](/docs/en/plugins/mods/test.md) covers stubbing a model call or the store, and testing timers and drawings.
341341 
342342## Share your mod
343343 
344344A mod is a plugin, so you version it in the manifest and people install and update it with the `/plugin` commands. How you share it depends on who it's for:
345345 
346* **A few people**: send them the plugin's directory or a `.zip` of it. See [Share a plugin without a marketplace](/docs/en/plugins/publish#share-a-plugin-without-a-marketplace)
347* **Your team**: list it in [your own marketplace](/docs/en/plugins/publish#publish-through-your-own-marketplace), such as a private repository with one directory for each plugin. To add that marketplace for everyone who works in a repository, [register it in the repository's settings](/docs/en/plugins/host-marketplace#register-the-marketplace-for-everyone-in-a-repository)
348* **Your whole organization**: an administrator can [install your organization's mods](/docs/en/plugins/mods/admin#install-your-organizations-mods) through managed settings
349* **Anyone**: make your marketplace's repository public, or [submit the plugin to Anthropic's directory](/docs/en/plugins/publish#submit-to-anthropics-directory)
346* **A few people**: send them the plugin's directory or a `.zip` of it. See [Share a plugin without a marketplace](/docs/en/plugins/publish.md#share-a-plugin-without-a-marketplace)
347* **Your team**: list it in [your own marketplace](/docs/en/plugins/publish.md#publish-through-your-own-marketplace), such as a private repository with one directory for each plugin. To add that marketplace for everyone who works in a repository, [register it in the repository's settings](/docs/en/plugins/host-marketplace.md#register-the-marketplace-for-everyone-in-a-repository)
348* **Your whole organization**: an administrator can [install your organization's mods](/docs/en/plugins/mods/admin.md#install-your-organizations-mods) through managed settings
349* **Anyone**: make your marketplace's repository public, or [submit the plugin to Anthropic's directory](/docs/en/plugins/publish.md#submit-to-anthropics-directory)
350350 
351Before you do, check the plugin's `name`: `claude plugin validate` fails a name that [looks like one of Anthropic's own](/docs/en/plugins/manifest-reference#name), such as one that starts with `claude-`. The events and methods can change between releases, so your README is the place to say which Claude Code version you tested with.
351Before you do, check the plugin's `name`: `claude plugin validate` fails a name that [looks like one of Anthropic's own](/docs/en/plugins/manifest-reference.md#name), such as one that starts with `claude-`. The events and methods can change between releases, so your README is the place to say which Claude Code version you tested with.
352352 
353353Keep developing against the directory with `--plugin-dir`, not against an installed copy. Claude Code caches an installed plugin by version, so your edits don't reach the installed copy until you increment the version and install again.
354354 
355355## Next steps
356356 
357* [Draw in the interface](/docs/en/plugins/mods/interface): open a pane, draw above the prompt, and add buttons and text fields
358* [React to events](/docs/en/plugins/mods/events): hook tool calls, prompts, and turns
359* [Use the mods API](/docs/en/plugins/mods/api): add commands and tools, call a model, and run work on a timer
360* [Test a mod](/docs/en/plugins/mods/test): stub what Claude Code would answer, and test timers and drawings
361* [Troubleshoot a mod](/docs/en/plugins/mods/troubleshoot): the reasons a mod does nothing, and the debug log
362* [Read the source of built-in mods](/docs/en/plugins/mods/overview#read-the-source-of-built-in-mods): complete plugins, each with its hooks module and tests
357* [Draw in the interface](/docs/en/plugins/mods/interface.md): open a pane, draw above the prompt, and add buttons and text fields
358* [React to events](/docs/en/plugins/mods/events.md): hook tool calls, prompts, and turns
359* [Use the mods API](/docs/en/plugins/mods/api.md): add commands and tools, call a model, and run work on a timer
360* [Test a mod](/docs/en/plugins/mods/test.md): stub what Claude Code would answer, and test timers and drawings
361* [Troubleshoot a mod](/docs/en/plugins/mods/troubleshoot.md): the reasons a mod does nothing, and the debug log
362* [Read the source of built-in mods](/docs/en/plugins/mods/overview.md#read-the-source-of-built-in-mods): complete plugins, each with its hooks module and tests
363363 
Feedback