Créer un mod
Demandez à Claude d'écrire un mod Claude Code à partir d'une description, ou écrivez-en un vous-même qui compte les appels d'outils et ajoute une commande. Apprenez la boucle de rechargement et de validation.
Un mod est un plugin Claude Code avec un fichier d'entrée, appelé le module de hooks : un fichier JavaScript ou TypeScript dont les fonctions Claude Code appelle lorsque des événements se produisent. Il y a deux façons d'en créer un :
- Demander à Claude de l'écrire : décrivez ce que vous voulez dans une session Claude Code
- L'écrire vous-même : suivez le tutoriel pour apprendre comment fonctionne le code d'un mod. Vous n'avez pas besoin de Node.js, d'un bundler ou d'une étape de build, car Claude Code charge les fichiers
.jset.tsdirectement.
Si vous n'avez pas encore décidé si un mod est le bon outil, lisez d'abord la comparaison sur la page d'aperçu.
Les mods nécessitent Claude Code v2.1.287 ou ultérieur. Dans votre shell, exécutez claude --version pour vérifier. Pour voir si les mods peuvent se charger pour vous, consultez Vérifier si les mods peuvent se charger.
Demander à Claude un mod
Décrivez le mod que vous voulez dans une session Claude Code interactive, et Claude l'écrit. Claude fonctionne à partir d'une skill intégrée nommée plugin-authoring, qui lui indique où écrire le mod, quels événements et méthodes votre version a, et comment le mod est chargé. Claude peut charger la skill lorsque vous demandez un mod, ou vous pouvez la charger vous-même en exécutant /plugin-authoring à l'invite Claude Code.
Le mod s'exécute une fois que vous l'approuvez, sauf dans les sessions où un mod que Claude écrit ne peut pas se charger.
Décrivez le mod
Demandez le mod avec vos propres mots, par exemple make a mod that shows the current git branch above the prompt. Claude écrit le mod dans un répertoire qui lui est propre dans le dossier des mods de la session, qui est ~/.claude/dev-mods/ suivi de l'ID de la session. Le chemin complet d'un mod ressemble à ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/.
Dans les modes de permission default et acceptEdits, Claude Code demande avant que Claude crée chacun des fichiers du mod, car ~/.claude est un chemin protégé. Approuvez chaque fichier au fur et à mesure.
Approuvez le mod
Lorsque Claude enregistre le premier fichier, Claude Code demande s'il faut activer le rechargement à chaud pour la session. Le rechargement à chaud exécute les mods que Claude écrit dans cette session et récupère chaque modification ultérieure.
Choisissez l'une de ces réponses :
- Activer pour cette session : les mods dans le dossier des mods de la session se chargent à la fin du tour, et se rechargent à la fin de chaque tour qui les modifie. Votre réponse dure pour la session, y compris après l'avoir reprise.
- Pas maintenant : rien ne se charge pour l'instant. Les fichiers restent où Claude les a écrits, et les mods se chargent la prochaine fois que cette session démarre. Pour empêcher un mod de jamais se charger, supprimez son répertoire.
Vérifiez que le mod s'est chargé
Exécutez /plugin à l'invite Claude Code et appuyez sur Tab jusqu'à ce que l'onglet Installed soit sélectionné. Il liste le mod, et vous pouvez l'éteindre là.
Essayez le mod
Utilisez ce que vous avez demandé. Pour l'exemple d'invite, le nom de la branche actuelle apparaît au-dessus de la boîte d'invite. Si le mod ne fait pas ce que vous vouliez, dites à Claude ce qu'il faut changer. Le mod se recharge à la fin de chaque tour qui modifie ses fichiers, vous pouvez donc essayer la modification dès que Claude a terminé.
Utilisez le mod dans d'autres sessions
Un mod que Claude a écrit ne se charge que dans la session qui l'a créé, et Claude Code supprime le dossier des mods de cette session une fois qu'il est plus ancien que cleanupPeriodDays. Pour conserver le mod, copiez son répertoire hors du dossier des mods vers un endroit qui vous appartient, comme ~/mods/git-branch. Ensuite, choisissez comment le charger :
- Dans une session que vous démarrez : dans votre shell, exécutez
claude --plugin-dir ~/mods/git-branch - Pour d'autres personnes : ajoutez-le à une marketplace pour qu'elles puissent l'installer
Sessions où un mod que Claude écrit ne peut pas se charger
Un mod que Claude écrit ne se charge qu'après que vous l'approuviez, dans un espace de travail de confiance où les mods sont autorisés à s'exécuter. Dans ces sessions, il ne se charge pas :
- Personne n'est là pour approuver : la session ne peut pas vous montrer une invite, comme dans une exécution
claude -pou en modedontAsk - L'espace de travail n'est pas de confiance : vous n'avez pas accepté l'invite de confiance pour le répertoire
- Les mods sont arrêtés : vous avez démarré avec
--safe-modeou--bare, vous avez définidisableAllHooks, ou les paramètres gérés de votre organisation le bloquent
Écrivez un mod vous-même
Dans ce tutoriel, vous construisez un mod nommé first-mod qui compte les appels d'outils que Claude fait, affiche le compte à côté du spinner pendant que Claude travaille, et ajoute une commande /tally qui l'imprime. Vous lisez ensuite les déclarations de type que Claude Code écrit à côté de votre mod et exécutez claude plugin validate. Ensemble, ils vous montrent les événements et méthodes que votre version offre et ce que Claude Code lit à partir de votre code.
Cet enregistrement montre le mod terminé. Le spinner compte les appels d'outils, /tally imprime le compte, et une modification du code prend effet pendant que la session s'exécute :
Vous écrivez trois fichiers :
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
plugin.json: le manifeste du pluginhooks.json: pointe vers votre fichier de coderegister.js: votre code, appelé le module de hooks
Créez le répertoire du plugin
Créez les deux répertoires qui contiennent les fichiers :
mkdir -p first-mod/.claude-plugin first-mod/hooks
New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
Écrivez le manifeste
Un mod est un plugin, et un mod a besoin d'un manifeste. Le manifeste de ce mod n'a pas de champs spéciaux. Enregistrez ceci comme first-mod/.claude-plugin/plugin.json :
{
"name": "first-mod",
"version": "0.1.0",
"description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
"author": { "name": "Your Name" }
}
Dites à Claude Code où se trouve votre code
Lorsque Claude Code charge un plugin, il lit le hooks/hooks.json du plugin. La clé modules dans ce fichier donne le chemin vers votre code, et l'avoir est ce qui rend le plugin un mod. Listez un chemin, relatif à hooks.json. Ici, il pointe vers register.js, que vous écrivez à l'étape suivante.
Enregistrez ceci comme first-mod/hooks/hooks.json :
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
Écrivez le code
Ce fichier est le code du mod, appelé le module de hooks. Lorsque le mod se charge, Claude Code appelle la fonction register que le fichier exporte et lui passe une fonction nommée on. Chaque appel à on enregistre un gestionnaire d'événement, appelé un hook, pour l'événement qu'il nomme.
Enregistrez ceci comme first-mod/hooks/register.js :
// The count, shared by the hooks below
let calls = 0
// Claude Code calls this once when the mod loads
export function register(on) {
// Runs when the session starts, before your first prompt
on('session.start', async ($, e, next) => {
// Add the /tally command
await $.command.register({
name: 'tally',
description: 'Show how many tool calls Claude has made',
})
// Let the session start as usual
return next(e)
})
// Runs each time Claude is about to use a tool
on('tool.call', async ($, e, next) => {
calls += 1
// Ask Claude Code to draw the interface again, so the new count shows
$.ui.invalidate('ui.render')
// Let the tool run as usual
return next(e)
})
// Runs when you type /tally, and only then, because of the matcher
on('command.run', { command: 'tally' }, async () => {
// The text to print in the transcript
return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
})
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}
Le fichier garde un compte dans calls et enregistre quatre hooks :
session.starts'exécute lorsque la session démarre, avant votre première invite, et à nouveau chaque fois que le mod se recharge. Il ajoute la commande/tallyà Claude Code.tool.calls'exécute chaque fois que Claude est sur le point d'utiliser un outil. Il ajoute un àcallset demande à Claude Code de redessiner l'interface.command.runs'exécute lorsque vous tapez/tally. Il retourne le texte à imprimer.ui.renders'exécute chaque fois que Claude Code dessine le spinner. Il ajoute le compte après le mot du spinner.
Comment fonctionne le mod d'exemple explique les trois arguments que chaque hook prend et ce que chacun retourne.
Chargez le mod
Démarrez Claude Code avec le drapeau --plugin-dir, qui charge un répertoire de plugin pour une session sans l'installer :
claude --plugin-dir ./first-mod
Essayez le mod
Demandez à Claude de faire quelque chose qui prend quelques appels d'outils, comme list the files here and read the README. Pendant que Claude travaille, le mot du spinner est suivi d'un compte qui augmente, comme dans Thinking · tool calls: 2…. Lorsque Claude a terminé, tapez /tally et appuyez sur Entrée. La transcription affiche first-mod: Claude has made 2 tool calls since this mod loaded, avec votre propre compte. Claude Code met le nom du plugin devant le texte de la commande.
Pour vérifier la commande sans une session interactive, exécutez-la en mode non-interactif :
claude -p "/tally" --plugin-dir ./first-mod
first-mod: Claude has made 0 tool calls since this mod loaded
Si /tally n'est pas dans la liste des commandes, le module ne s'est pas chargé. Consultez Découvrez pourquoi un mod ne fait rien.
Modifiez le code pendant que la session s'exécute
Laissez la session ouverte. Dans register.js, changez ' · tool calls: ' en ' · tools used: ' dans le hook ui.render et enregistrez. La ligne en surbrillance est celle qui change :
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
})
Une ligne dans la transcription dit que first-mod s'est rechargé et liste ses hooks, et le prochain spinner utilise le nouveau texte, comme dans Thinking · tools used: 1….
Comment fonctionne le mod d'exemple
Chaque fonction que vous passez à on est un hook, qui est un gestionnaire d'événement. Claude Code passe à chaque hook les trois mêmes arguments :
- L'API des mods, nommée
$: chaque méthode qu'un mod peut appeler pour atteindre l'extérieur de lui-même, dans des espaces de noms tels que$.uiet$.command - L'événement, nommé
e: l'entrée de l'événement en tant que données simples, comme le nom et les arguments d'un appel d'outil - Le gestionnaire suivant, nommé
next: une fonction qui transmet l'événement aux autres mods puis au comportement propre de Claude Code, et retourne le résultat
Les hooks dans first-mod gèrent leurs événements de trois façons qu'un hook peut :
- Observer : le hook
session.startenregistre la commande, et le hooktool.callcompte l'appel et demande un redessinage. Les deux retournentnext(e), donc la session démarre et l'outil s'exécute comme d'habitude. - Répondre : le hook
command.runretourne son propre résultat et n'appelle jamaisnext. Le deuxième argument àon,{ command: 'tally' }, est un filtre, appelé un matcher, donc le hook s'exécute uniquement pour/tally. - Réécrire : le hook
ui.renderappellenextavec une copie deedontsuffixcontient le compte, donc Claude Code dessine son spinner habituel avec votre texte après le mot
Claude Code surveille un répertoire chargé avec --plugin-dir et recharge à chaud le module de hooks lorsqu'un fichier dedans change. Chaque rechargement exécute register à nouveau, donc calls revient à 0 et /tally recommence à compter. Pour conserver une valeur entre les rechargements, consultez Conserver l'état.
Continuez à travailler sur un mod
Une fois qu'un mod se charge, vous pouvez demander à Claude de le modifier, vérifier votre code par rapport aux définitions de type pour votre version, lister les événements et appels que Claude Code trouve dedans, et le tester.
Modifiez un mod avec Claude
Pour modifier un mod que vous avez déjà, démarrez la session avec --plugin-dir pointé vers le répertoire du mod, afin que ce que Claude écrit se charge dans la même session :
claude --plugin-dir ./first-mod
Ensuite, demandez la modification, par exemple add a /tally-reset command to this mod that sets the tally back to zero. Claude édite le module de hooks, exécute claude plugin validate, et corrige ce qu'il rapporte. Un répertoire que vous chargez avec --plugin-dir est un chemin protégé, donc en modes default et acceptEdits vous êtes invité à approuver chacune des modifications de Claude au mod. Le tableau des chemins protégés donne le résultat pour les autres modes de permission.
Les fichiers que Claude enregistre pendant son tour se rechargent à la fin du tour, vous pouvez donc essayer /tally-reset dès que Claude a terminé.
Obtenez les définitions de type pour votre version
Chaque fois que Claude Code charge ou recharge un mod à partir d'un répertoire que vous passez à --plugin-dir, ou un mod que Claude a écrit pour vous, il écrit des fichiers de déclaration TypeScript, se terminant par .d.ts, dans .claude-plugin/types/ à l'intérieur du répertoire du mod. Ils décrivent les événements exacts, les méthodes de l'API des mods, et les éléments dans la version de Claude Code que vous exécutez, afin que votre éditeur puisse autocomplète et vérifier le type de vos hooks. Pour parcourir les déclarations en ligne, lisez mods/types/claude-code.d.ts dans le référentiel Claude Code, dont la première ligne nomme la version qui l'a écrit. Le répertoire contient ces fichiers :
| Chemin | Ce qu'il déclare |
|---|---|
claude-code/index.d.ts |
Chaque événement et son entrée et résultat, chaque espace de noms et méthode de l'API des mods, et les éléments que chaque surface peut dessiner |
claude-code-tools/index.d.ts |
Les entrées et résultats des outils intégrés, afin que vérifier e.tool === 'Bash' réduise e |
claude-code-mcp/index.d.ts |
Les entrées des outils MCP qui ont été connectés la dernière fois que vous avez enregistré un fichier dans le mod |
index.d.ts dans un répertoire nommé pour un plugin |
Ce que ce plugin ajoute à l'API des mods. Il y a un répertoire pour chaque plugin que votre plugin.json liste sous dependencies. |
tsconfig.json |
Les options du compilateur qui conviennent à un module de hooks |
Si votre mod n'a pas son propre tsconfig.json, Claude Code en ajoute un à la racine du mod qui étend le généré, afin que votre éditeur et tsc -p ./first-mod vérifient le type du mod sans plus de configuration.
Les événements et méthodes peuvent changer entre les versions, donc faites confiance à ces fichiers plutôt qu'à n'importe quelle page, celle-ci incluse, lorsqu'ils ne sont pas d'accord.
claude-code/index.d.ts est la référence la plus complète pour votre build, avec un commentaire et un exemple pour chaque méthode de l'API des mods. Pour chercher quelque chose, recherchez le fichier pour son nom, comme 'tool.call'.
Vérifiez ce que Claude Code lit à partir de votre mod
Pour voir votre mod comme Claude Code le voit, sans exécuter votre code ou démarrer une session, utilisez claude plugin validate. Il vérifie le manifeste et exécute la même analyse statique sur la source du module de hooks que Claude Code exécute lorsqu'il charge un mod. Dans votre shell, exécutez-le sur le répertoire du mod :
claude plugin validate ./first-mod
Pour first-mod, la sortie inclut ces lignes.
❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passed
La ligne hooks: liste les événements que votre module hook, chacun avec son filtre entre accolades. La ligne calls: liste chaque méthode de l'API des mods qu'il appelle. Un module qui lit ou définit des variables d'environnement obtient également des lignes env reads: et env writes:, et un qui utilise $.state obtient state reads: et state writes:.
Si un événement que vous aviez l'intention de hooker manque de la première ligne, Claude Code n'appellera pas ce hook non plus. La cause habituelle est un nom d'événement mal orthographié, que la commande rapporte comme une erreur telle que "tool.calls" is not an event.
Suivez ces règles afin que l'analyse statique puisse trouver chaque hook et appel :
- Épellez chaque appel de l'API des mods en entier :
$, l'espace de noms, puis la méthode, comme dans$.store.get('notes'). Vous pouvez passer$à une fonction déclarée au niveau supérieur du même fichier, et pour une fonction vôtre nomméeloadNotes, la lignecalls:lit alors$.store.get (via loadNotes). Passer$à une méthode, une fonction définie à l'intérieur du hook, ou une fonction que vous importez d'un autre de vos fichiers échoue la validation. Les fonctionsreadetupdateque$.stateutilise sont les imports qui peuvent le prendre. N'assignez pas$ou l'un de ses espaces de noms à une variable, ne le déstructurez pas, ou ne l'indexez pas avec un nom calculé.const ui = $.uiéchoue avec$.ui is used as a value. - Écrivez le nom de l'événement dans chaque appel
oncomme un littéral de chaîne, comme'tool.call'. Une variable, ou une boucle sur une liste de noms, échoue avecthe event name passed to on() is not a string literal. - À l'intérieur de
register, ne déclarez pas une deuxième variable ou paramètre nomméon. La validation échoue avec"on" is declared again (shadowed). - Importez uniquement à partir de fichiers à l'intérieur du répertoire du plugin, par chemin relatif. Le seul import nu autorisé est
claude-code, pour les types et quelques helpers. - Utilisez les déclarations
importen haut du fichier, comme dansimport { name } from './file.js'. Unimport()dynamique échoue aveca dynamic import(); a hooks module imports its own files with an import declaration. - Écrivez chaque fichier en tant que module ES, avec
importet nonrequire. La référence liste les extensions de fichier que Claude Code charge.
Testez le mod
Vous pouvez écrire des tests automatisés pour un mod et les exécuter à partir de votre shell avec claude plugin test, sans session, connexion ou réseau. Un test lève les événements que vos hooks gèrent et vérifie ce que les hooks ont fait.
Ce test lève deux appels d'outils, exécute /tally, et vérifie que la réponse compte les deux. Enregistrez-le comme first-mod/tests/first-mod.test.ts :
import { expect, test } from 'claude-code/testing'
test('/tally reports the tool calls the mod has seen', async ($, on) => {
// Answer each tool call in Claude Code's place, so no tool runs
on('tool.call', () => ({ result: 'ok' }))
// Raise two tool calls, which the mod's tool.call hook counts
await $.tool.call({ tool: 'Bash', command: 'ls' })
await $.tool.call({ tool: 'Read', file_path: 'README.md' })
// Run /tally and check the text its hook returns
const answer = await $.command.run({ command: 'tally', args: '' })
expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
Dans votre shell, exécutez les tests à partir du répertoire first-mod :
claude plugin test
La sortie nomme chaque test et s'il a réussi, avec des timings qui varient d'une exécution à l'autre :
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [22.87ms]
1 pass
0 fail
Ran 1 test across 1 file. [0.19s]
Testez un mod couvre le stubbing d'un appel de modèle ou du store, et le test des minuteurs et des dessins.
Partagez votre mod
Un mod est un plugin, donc vous le versionnez dans le manifeste et les gens l'installent et le mettent à jour avec les commandes /plugin. Pour le donner à d'autres personnes, ajoutez-le à une marketplace.
Avant de le faire, vérifiez le name du plugin : claude plugin validate échoue un nom qui ressemble à l'un des propres d'Anthropic, comme un qui commence par claude-. Les événements et méthodes peuvent changer entre les versions, donc votre README est l'endroit pour dire quelle version de Claude Code vous avez testée.
Continuez à développer contre le répertoire avec --plugin-dir, pas contre une copie installée. Claude Code met en cache un plugin installé par version, donc vos modifications n'atteignent pas la copie installée jusqu'à ce que vous augmentiez la version et réinstalliez.
Prochaines étapes
- Dessinez dans l'interface : ouvrez un volet, dessinez au-dessus de l'invite, et ajoutez des boutons et des champs de texte
- Réagissez aux événements : hooquez les appels d'outils, les invites, et les tours
- Utilisez l'API des mods : ajoutez des commandes et des outils, appelez un modèle, et exécutez du travail sur un minuteur
- Testez un mod : stubifiez ce que Claude Code répondrait, et testez les minuteurs et les dessins
- Dépannez un mod : les raisons pour lesquelles un mod ne fait rien, et le journal de débogage
- Lisez la source des mods intégrés : des plugins complets, chacun avec son module de hooks et ses tests