Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 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 20 Oct 2026 18:37 UTC.

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

### Sessions where a mod Claude writes can't load ### Get type definitions for your version

The whole hunk

from line 9, old and new numbered
/
lines
from line 9
99 
1010If 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.
1111 
12<Note>
13 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</Note>
12> [!NOTE]
13>
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).
1515 
1616## Ask Claude for a mod
1717 
from line 19
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 
22<Steps>
23 <Step title="Describe the mod">
24 Ask for the mod in your own words, for example `make a mod that shows the current git branch above the prompt`. Claude writes the mod in a directory of its own in the session's mods folder, which is `~/.claude/dev-mods/` followed by the session's ID. A mod's full path looks like `~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/`.
22**1. Describe the mod**
2523 
26 <Note>
27 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 </Note>
29 </Step>
24Ask for the mod in your own words, for example `make a mod that shows the current git branch above the prompt`. Claude writes the mod in a directory of its own in the session's mods folder, which is `~/.claude/dev-mods/` followed by the session's ID. A mod's full path looks like `~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/`.
3025 
31 <Step title="Approve the mod">
32 When Claude saves the first file, Claude Code asks whether to enable hot reloading for the session. Hot reloading runs the mods Claude writes in this session and picks up each later change.
26> [!NOTE]
27>
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.
3329 
34 Choose one of these answers:
30**2. Approve the mod**
3531 
36 * **Enable for this session**: the mods in the session's mods folder load when the turn ends, and reload at the end of each turn that changes them. Your answer lasts for the session, including after you resume it.
37 * **Not now**: nothing loads for now. The files stay where Claude wrote them, and the mods load the next time that session starts. To keep a mod from ever loading, delete its directory.
38 </Step>
32When Claude saves the first file, Claude Code asks whether to enable hot reloading for the session. Hot reloading runs the mods Claude writes in this session and picks up each later change.
3933 
40 <Step title="Check that the mod loaded">
41 Run `/plugin` at the Claude Code prompt and press Tab until the **Installed** tab is selected. It lists the mod, and you can turn it off there.
42 </Step>
34Choose one of these answers:
4335 
44 <Step title="Try the mod">
45 Use what you asked for. For the example prompt, the current branch name appears above the prompt box. If the mod doesn't do what you wanted, tell Claude what to change. The mod reloads at the end of each turn that changes its files, so you can try the change as soon as Claude finishes.
46 </Step>
47</Steps>
36* **Enable for this session**: the mods in the session's mods folder load when the turn ends, and reload at the end of each turn that changes them. Your answer lasts for the session, including after you resume it.
37* **Not now**: nothing loads for now. The files stay where Claude wrote them, and the mods load the next time that session starts. To keep a mod from ever loading, delete its directory.
4838 
39**3. Check that the mod loaded**
40 
41Run `/plugin` at the Claude Code prompt and press Tab until the **Installed** tab is selected. It lists the mod, and you can turn it off there.
42 
43**4. Try the mod**
44 
45Use what you asked for. For the example prompt, the current branch name appears above the prompt box. If the mod doesn't do what you wanted, tell Claude what to change. The mod reloads at the end of each turn that changes its files, so you can try the change as soon as Claude finishes.
46 
4947### Use the mod in other sessions
5048 
5149A mod Claude wrote loads only in the session that made it, and Claude Code deletes that session's mods folder once it's older than [`cleanupPeriodDays`](/docs/en/settings-reference#cleanupperioddays). To keep the mod, copy its directory out of the mods folder to a place of your own, such as `~/mods/git-branch`. Then choose how to load it:
from line 51
5351* **In a session you start**: in your shell, run `claude --plugin-dir ~/mods/git-branch`
5452* **For other people**: [add it to a marketplace](#share-your-mod) so they can install it
5553 
56<h3 id="sessions-that-skip-the-approval">
57 Sessions where a mod Claude writes can't load
58</h3>
54### Sessions where a mod Claude writes can't load
5955 
6056A mod Claude writes loads only after you approve it, in a trusted workspace where mods are allowed to run. In these sessions it doesn't load:
6157 
from line 65
6965 
7066This recording shows the finished mod. The spinner counts tool calls, `/tally` prints the count, and an edit to the code takes effect while the session runs:
7167 
72<Frame>
73 <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-first-mod-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=eb561134afa90375777408453ba51c77" aria-label="In a Claude Code session, the prompt 'list the files here and read the README' is typed and sent. The spinner reads 'Thinking · tool calls: 1' and the count rises as Claude works. The /tally command prints 'first-mod: Claude has made 3 tool calls since this mod loaded'. A line says first-mod reloaded and lists its four hooks. On the next prompt the spinner reads 'Thinking · tools used: 1'." data-path="images/mods-first-mod-light.mp4" />
68<video autoPlay muted loop playsInline controls src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-first-mod-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=eb561134afa90375777408453ba51c77" aria-label="In a Claude Code session, the prompt 'list the files here and read the README' is typed and sent. The spinner reads 'Thinking · tool calls: 1' and the count rises as Claude works. The /tally command prints 'first-mod: Claude has made 3 tool calls since this mod loaded'. A line says first-mod reloaded and lists its four hooks. On the next prompt the spinner reads 'Thinking · tools used: 1'." />
7469 
75 <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-first-mod-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=09779dadc7ef66c2b1e2da0c2e31ac72" aria-label="In a Claude Code session, the prompt 'list the files here and read the README' is typed and sent. The spinner reads 'Thinking · tool calls: 1' and the count rises as Claude works. The /tally command prints 'first-mod: Claude has made 3 tool calls since this mod loaded'. A line says first-mod reloaded and lists its four hooks. On the next prompt the spinner reads 'Thinking · tools used: 1'." data-path="images/mods-first-mod-dark.mp4" />
76</Frame>
77 
7870You write three files:
7971 
8072```text theme={null}
from line 82
9082* **`hooks.json`**: [points to your code file](/docs/en/plugins/mods/reference#files)
9183* **`register.js`**: your code, called the hooks module
9284 
93<Steps>
94 <Step title="Create the plugin directory">
95 Create the two directories that hold the files:
85**1. Create the plugin directory**
9686 
97 <Tabs>
98 <Tab title="Bash or Zsh">
99 ```bash theme={null}
100 mkdir -p first-mod/.claude-plugin first-mod/hooks
101 ```
102 </Tab>
87Create the two directories that hold the files:
10388 
104 <Tab title="PowerShell">
105 ```powershell theme={null}
106 New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
107 ```
108 </Tab>
109 </Tabs>
110 </Step>
89**Bash or Zsh**
11190 
112 <Step title="Write the manifest">
113 A 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`:
91```bash theme={null}
92mkdir -p first-mod/.claude-plugin first-mod/hooks
93```
11494 
115 ```json first-mod/.claude-plugin/plugin.json theme={null}
116 {
117 "name": "first-mod",
118 "version": "0.1.0",
119 "description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
120 "author": { "name": "Your Name" }
121 }
122 ```
123 </Step>
95**PowerShell**
12496 
125 <Step title="Tell Claude Code where your code is">
126 When Claude Code loads a plugin, it reads the plugin's `hooks/hooks.json`. The `modules` key in that file gives the path to your code, and having it is what makes the plugin a mod. List one path, relative to `hooks.json`. Here it points to `register.js`, which you write in the next step.
97```powershell theme={null}
98New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
99```
127100 
128 Save this as `first-mod/hooks/hooks.json`:
101**2. Write the manifest**
129102 
130 ```json first-mod/hooks/hooks.json theme={null}
131 {
132 "description": "The first-mod hooks module",
133 "modules": ["./register.js"]
134 }
135 ```
136 </Step>
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`:
137104 
138 <Step title="Write the code">
139 This file is the mod's code, called the hooks module. When the mod loads, Claude Code calls the `register` function the file exports and passes it a function named [`on`](/docs/en/plugins/mods/reference#the-hook-function). Each call to `on` registers an event handler, called a hook, for the event it names.
105```json first-mod/.claude-plugin/plugin.json theme={null}
106{
107 "name": "first-mod",
108 "version": "0.1.0",
109 "description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
110 "author": { "name": "Your Name" }
111}
112```
140113 
141 Save this as `first-mod/hooks/register.js`:
114**3. Tell Claude Code where your code is**
142115 
143 ```javascript first-mod/hooks/register.js theme={null}
144 // The count, shared by the hooks below
145 let calls = 0
116When Claude Code loads a plugin, it reads the plugin's `hooks/hooks.json`. The `modules` key in that file gives the path to your code, and having it is what makes the plugin a mod. List one path, relative to `hooks.json`. Here it points to `register.js`, which you write in the next step.
146117 
147 // Claude Code calls this once when the mod loads
148 export function register(on) {
149 // Runs when the session starts, before your first prompt
150 on('session.start', async ($, e, next) => {
151 // Add the /tally command
152 await $.command.register({
153 name: 'tally',
154 description: 'Show how many tool calls Claude has made',
155 })
156 // Let the session start as usual
157 return next(e)
158 })
118Save this as `first-mod/hooks/hooks.json`:
159119 
160 // Runs each time Claude is about to use a tool
161 on('tool.call', async ($, e, next) => {
162 calls += 1
163 // Ask Claude Code to draw the interface again, so the new count shows
164 $.ui.invalidate('ui.render')
165 // Let the tool run as usual
166 return next(e)
167 })
120```json first-mod/hooks/hooks.json theme={null}
121{
122 "description": "The first-mod hooks module",
123 "modules": ["./register.js"]
124}
125```
168126 
169 // Runs when you type /tally, and only then, because of the matcher
170 on('command.run', { command: 'tally' }, async () => {
171 // The text to print in the transcript
172 return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
173 })
127**4. Write the code**
174128 
175 // Runs each time Claude Code draws the spinner
176 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
177 // Keep Claude Code's spinner, with the count added after its word
178 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
179 })
180 }
181 ```
129This file is the mod's code, called the hooks module. When the mod loads, Claude Code calls the `register` function the file exports and passes it a function named [`on`](/docs/en/plugins/mods/reference#the-hook-function). Each call to `on` registers an event handler, called a hook, for the event it names.
182130 
183 The file keeps a count in `calls` and registers four hooks:
131Save this as `first-mod/hooks/register.js`:
184132 
185 * **[`session.start`](/docs/en/plugins/mods/reference#session)** runs when the session starts, before your first prompt, and again each time the mod reloads. It adds the `/tally` command to Claude Code.
186 * **[`tool.call`](/docs/en/plugins/mods/reference#tools)** runs each time Claude is about to use a tool. It adds one to `calls` and asks Claude Code to draw the interface again.
187 * **[`command.run`](/docs/en/plugins/mods/reference#commands-and-configuration)** runs when you type `/tally`. It returns the text to print.
188 * **[`ui.render`](/docs/en/plugins/mods/reference#interface)** runs each time Claude Code draws the spinner. It adds the count after the spinner's word.
133```javascript first-mod/hooks/register.js theme={null}
134// The count, shared by the hooks below
135let calls = 0
189136 
190 [How the example mod works](#how-the-example-mod-works) explains the three arguments each hook takes and what each one returns.
191 </Step>
137// Claude Code calls this once when the mod loads
138export function register(on) {
139 // Runs when the session starts, before your first prompt
140 on('session.start', async ($, e, next) => {
141 // Add the /tally command
142 await $.command.register({
143 name: 'tally',
144 description: 'Show how many tool calls Claude has made',
145 })
146 // Let the session start as usual
147 return next(e)
148 })
192149 
193 <Step title="Load the mod">
194 Start Claude Code with the `--plugin-dir` flag, which loads a plugin directory for one session without installing it:
150 // Runs each time Claude is about to use a tool
151 on('tool.call', async ($, e, next) => {
152 calls += 1
153 // Ask Claude Code to draw the interface again, so the new count shows
154 $.ui.invalidate('ui.render')
155 // Let the tool run as usual
156 return next(e)
157 })
195158 
196 ```bash theme={null}
197 claude --plugin-dir ./first-mod
198 ```
199 </Step>
159 // Runs when you type /tally, and only then, because of the matcher
160 on('command.run', { command: 'tally' }, async () => {
161 // The text to print in the transcript
162 return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
163 })
200164 
201 <Step title="Try the mod">
202 Ask Claude to do something that takes a few tool calls, such as `list the files here and read the README`. While Claude works, the spinner's word is followed by a count that rises, as in `Thinking · tool calls: 2…`. When Claude finishes, type `/tally` and press Enter. The transcript shows `first-mod: Claude has made 2 tool calls since this mod loaded`, with your own count. Claude Code puts the plugin's name in front of the command's text.
165 // Runs each time Claude Code draws the spinner
166 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
167 // Keep Claude Code's spinner, with the count added after its word
168 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
169 })
170}
171```
203172 
204 To check the command without an interactive session, run it in non-interactive mode:
173The file keeps a count in `calls` and registers four hooks:
205174 
206 ```bash theme={null}
207 claude -p "/tally" --plugin-dir ./first-mod
208 ```
175* **[`session.start`](/docs/en/plugins/mods/reference#session)** runs when the session starts, before your first prompt, and again each time the mod reloads. It adds the `/tally` command to Claude Code.
176* **[`tool.call`](/docs/en/plugins/mods/reference#tools)** runs each time Claude is about to use a tool. It adds one to `calls` and asks Claude Code to draw the interface again.
177* **[`command.run`](/docs/en/plugins/mods/reference#commands-and-configuration)** runs when you type `/tally`. It returns the text to print.
178* **[`ui.render`](/docs/en/plugins/mods/reference#interface)** runs each time Claude Code draws the spinner. It adds the count after the spinner's word.
209179 
210 ```text theme={null}
211 first-mod: Claude has made 0 tool calls since this mod loaded
212 ```
180[How the example mod works](#how-the-example-mod-works) explains the three arguments each hook takes and what each one returns.
213181 
214 If `/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).
215 </Step>
182**5. Load the mod**
216183 
217 <Step title="Change the code while the session runs">
218 Leave the session open. In `register.js`, change `' · tool calls: '` to `' · tools used: '` in the `ui.render` hook and save. The highlighted line is the one that changes:
184Start Claude Code with the `--plugin-dir` flag, which loads a plugin directory for one session without installing it:
219185 
220 ```javascript first-mod/hooks/register.js {4} theme={null}
221 // Runs each time Claude Code draws the spinner
222 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
223 // Keep Claude Code's spinner, with the count added after its word
224 return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
225 })
226 ```
186```bash theme={null}
187claude --plugin-dir ./first-mod
188```
227189 
228 A line in the transcript says `first-mod` reloaded and lists its hooks, and the next spinner uses the new text, as in `Thinking · tools used: 1…`.
229 </Step>
230</Steps>
190**6. Try the mod**
231191 
192Ask Claude to do something that takes a few tool calls, such as `list the files here and read the README`. While Claude works, the spinner's word is followed by a count that rises, as in `Thinking · tool calls: 2…`. When Claude finishes, type `/tally` and press Enter. The transcript shows `first-mod: Claude has made 2 tool calls since this mod loaded`, with your own count. Claude Code puts the plugin's name in front of the command's text.
193 
194To check the command without an interactive session, run it in non-interactive mode:
195 
196```bash theme={null}
197claude -p "/tally" --plugin-dir ./first-mod
198```
199 
200```text theme={null}
201first-mod: Claude has made 0 tool calls since this mod loaded
202```
203 
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).
205 
206**7. Change the code while the session runs**
207 
208Leave the session open. In `register.js`, change `' · tool calls: '` to `' · tools used: '` in the `ui.render` hook and save. The highlighted line is the one that changes:
209 
210```javascript first-mod/hooks/register.js {4} theme={null}
211 // Runs each time Claude Code draws the spinner
212 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
213 // Keep Claude Code's spinner, with the count added after its word
214 return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
215 })
216```
217 
218A line in the transcript says `first-mod` reloaded and lists its hooks, and the next spinner uses the new text, as in `Thinking · tools used: 1…`.
219 
232220### How the example mod works
233221 
234222Each function you pass to `on` is a hook, which is an event handler. Claude Code passes every hook the same three arguments:
from line 249
261249 
262250Files Claude saves during its turn reload when the turn ends, so you can try `/tally-reset` as soon as Claude finishes.
263251 
264<h3 id="get-the-types-for-your-build">
265 Get type definitions for your version
266</h3>
252### Get type definitions for your version
267253 
268254When Claude Code loads a mod from `--plugin-dir` in an interactive session, or a mod [Claude wrote for you](#ask-claude-for-a-mod), it writes TypeScript declaration files into the mod's `.claude-plugin/types/` directory. They describe the exact events, mods API methods, and elements in the Claude Code version you're running, so your editor can autocomplete and type-check your hooks. The directory holds these files:
269255 
Feedback