Creer un plugin
De zero a un plugin data complet : structure de dossiers, manifeste, composants, test en local, avec l'exemple integral de
boite-a-outils-data.
Temps de lecture : 15 min | Niveau : Intermediaire
Ce que tu sauras faire apres
- Creer la structure de dossiers d'un plugin et son manifeste.
- Remplir les champs de
plugin.jsonsans en inventer aucun. - Placer correctement skills, agents, hooks et serveur MCP.
- Tester ton plugin en local avec
--plugin-dir, sans marketplace. - Diagnostiquer un composant qui ne se charge pas.
1. Le squelette minimal
Un plugin, c'est un dossier avec un plugin.json dedans. Rien de plus pour commencer.
mkdir -p boite-a-outils-data/.claude-pluginPuis boite-a-outils-data/.claude-plugin/plugin.json :
{
"name": "boite-a-outils-data",
"description": "Skills, agent et hooks pour le travail SQL, dbt et pipelines",
"version": "1.0.0",
"author": {
"name": "Ton Nom"
}
}Quatre champs, quatre roles :
name: obligatoire. Il identifie le plugin et devient le prefixe de chaque skill et agent. Pas d'espaces. Ecris-le en kebab-case, sinonclaude plugin validatet'avertit.description: le texte affiche pour ton plugin dans/plugin.version: optionnel. Le fixer garde les utilisateurs sur cette version tant que tu ne la changes pas.author: exige unnamea l'interieur.emaileturlsont optionnels.
Rappel qui coute cher quand on l'oublie : seul
plugin.jsonva dans.claude-plugin/. Les composants ranges la-dedans ne se chargent pas. Et la racine du plugin est le dossier du plugin lui-meme, pas~/.claude/.
2. Les champs du manifeste
Le tableau ci-dessous reprend les champs que tu vas reellement utiliser. La reference officielle en liste davantage.
Metadonnees
| Champ | Type | Role |
|---|---|---|
name | String | Identifiant, obligatoire, kebab-case |
displayName | String | Nom affiche dans l'interface a la place de name |
version | String | Chaine de version |
description | String | Explication courte de ce que fournit le plugin |
author | Object | name obligatoire, email et url optionnels |
homepage | String | URL de documentation. Doit etre une URL valide, sinon le plugin ne se charge pas |
repository | String | URL du depot source. Non validee |
license | String | Identifiant SPDX comme MIT ou Apache-2.0 |
keywords | Array de strings | Etiquettes pour la recherche |
defaultEnabled | Boolean | Si le plugin demarre actif quand l'utilisateur n'a rien regle. Defaut true |
dependencies | Array de strings ou d'objets | Plugins qui doivent etre actifs pour que celui-ci fonctionne |
metadata | Object | Objet libre pour tes propres donnees. Claude Code ne le lit pas |
$schema | String | URL de schema JSON pour l'autocompletion dans ton editeur. Ignoree au chargement |
Champs de composants
Tu n'en as besoin que si tu ranges un composant ailleurs que dans son emplacement par defaut.
| Champ | Comportement |
|---|---|
skills | Chemin ou tableau de chemins. S'ajoute au scan par defaut de skills/ |
commands | Chemin, tableau, ou objet associant un nom de commande a source ou content. Remplace le scan de commands/ |
agents | Chemin ou tableau de fichiers .md. Les dossiers ne sont pas acceptes. Remplace le scan de agents/ |
hooks | Chemin, objet inline, ou tableau. Charge en plus de hooks/hooks.json |
mcpServers | Chemin, objet inline, ou tableau. Charge en plus de .mcp.json ; un serveur declare plus tard remplace un homonyme |
lspServers | Meme logique, charge en plus de .lsp.json |
outputStyles | Remplace le scan de output-styles/ |
workflows | Fichiers .js de workflow. Remplace le scan de workflows/ |
Retiens la nuance : skills, hooks et mcpServers s'ajoutent au defaut ; commands, agents, outputStyles et workflows remplacent le defaut. Quand tu remplaces un dossier par defaut qui existe quand meme, Claude Code t'avertit avec Default <dossier>/ folder is ignored because the manifest sets "<cle>".
Trois regles de chemin a respecter :
- Commence chaque chemin par
./. Un chemin ecritcommands/deploy.mdechoue a la validation. Seulskillsaccepte aussi".", qui designe la racine du plugin. - Utilise des barres obliques vers l'avant, comme
./commands/deploy.md. Sur macOS et Linux, un chemin contenant une barre inverse est rejete, meme s'il reste dans le plugin. - Ne sors pas de la racine du plugin. Un chemin comme
../shared-utilsest refuse. L'erreur estpath escapes plugin directoryet le plugin se charge sans ce composant.
3. Exemple complet : boite-a-outils-data 🧰
On construit un plugin utile au quotidien pour un data engineer : une skill de revue SQL, une skill de documentation dbt, un agent d'audit de pipeline, une commande de profilage, un hook de verification et un serveur MCP.
L'arborescence
boite-a-outils-data/
├── .claude-plugin/
│ └── plugin.json
├── skills/
│ ├── revue-sql/
│ │ └── SKILL.md
│ └── doc-modele-dbt/
│ └── SKILL.md
├── agents/
│ └── auditeur-pipeline.md
├── commands/
│ └── profil-table.md
├── hooks/
│ └── hooks.json
├── scripts/
│ └── verifie_sql.sh
├── .mcp.json
└── README.mdNote que scripts/ n'est pas un emplacement special : c'est un dossier ordinaire que les hooks appellent via ${CLAUDE_PLUGIN_ROOT}.
Le manifeste
{
"name": "boite-a-outils-data",
"displayName": "Boite a outils Data",
"description": "Revue SQL, documentation dbt, audit de pipeline et garde-fous pour le travail data",
"version": "1.0.0",
"author": {
"name": "Ton Nom",
"email": "toi@exemple.fr"
},
"license": "MIT",
"keywords": ["sql", "dbt", "airflow", "data-engineering"]
}Pas de champ de composant ici : tout est a son emplacement par defaut, donc Claude Code les trouve seul.
Skill 1 — skills/revue-sql/SKILL.md
Une skill est un fichier SKILL.md que Claude peut charger quand sa description correspond a ta tache. Donne-lui toujours une description, c'est elle qui declenche.
---
name: revue-sql
description: Relit une requete SQL analytique pour la correction, la performance et la lisibilite. A utiliser quand on te demande de relire, optimiser ou valider du SQL.
---
Tu relis une requete SQL analytique. Procede dans cet ordre.
1. Correction : cle et type de chaque jointure, risque de duplication de lignes,
colonnes non agregees absentes du GROUP BY, comparaisons a NULL faites avec =.
2. Performance : SELECT * sur une table de faits, fonction appliquee a une colonne
filtree qui casse l'usage d'index, sous-requete correlee remplacable par une
jointure ou une fonction de fenetre.
3. Lisibilite : CTE nommes a la place des sous-requetes imbriquees, alias explicites.
Rends un tableau : Ligne | Probleme | Correction proposee | Gravite.
Puis donne la requete corrigee complete dans un bloc de code sql.
Reponds en francais.Skill 2 — skills/doc-modele-dbt/SKILL.md
---
name: doc-modele-dbt
description: Genere le fichier schema.yml d'un modele dbt a partir de son SQL. A utiliser quand on te demande de documenter un modele dbt ou d'ajouter des tests dbt.
---
Tu documentes un modele dbt.
1. Lis le fichier SQL du modele indique et deduis ses colonnes de sortie.
2. Produis un bloc yaml pour schema.yml contenant le nom du modele, sa description
en une phrase, une description par colonne en francais sans jargon, et les tests
pertinents : unique et not_null sur la cle primaire, relationships sur chaque cle
etrangere, accepted_values sur les colonnes de statut.
3. Ne propose jamais un test que tu ne peux pas justifier par le SQL lu.
Termine en listant les colonnes dont tu n'as pas pu deduire le sens.
Reponds en francais.Agent — agents/auditeur-pipeline.md
Un subagent est un assistant separe, avec ses propres instructions et sa propre fenetre de contexte, a qui Claude peut deleguer une tache.
---
name: auditeur-pipeline
description: Audite un DAG Airflow ou un projet dbt pour la robustesse et l'idempotence. A utiliser apres une modification d'orchestration.
model: sonnet
---
Tu es auditeur de pipelines de donnees. Verifie ces points un par un :
1. Idempotence : relancer une execution sur la meme partition donne-t-il le meme resultat ?
2. Fenetres de donnees : les dates viennent-elles du contexte d'execution ou sont-elles codees en dur ?
3. Gestion d'echec : retries, timeout et alerte sur chaque tache ?
4. Dependances : une tache lit-elle une table qu'une autre n'a pas encore ecrite dans le meme run ?
5. Tests de donnees : verification de fraicheur et de volumetrie apres chargement ?
Rends un rapport avec, pour chaque point : Statut (OK / A corriger / Non applicable),
Preuve (fichier et ligne), Correction proposee.
Ne modifie aucun fichier. Tu audites, tu ne corriges pas. Reponds en francais.Cet agent s'appelle boite-a-outils-data:auditeur-pipeline, et tu l'invoques explicitement avec @agent-boite-a-outils-data:auditeur-pipeline.
Les champs de frontmatter supportes pour un agent de plugin sont name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, omitClaudeMd, isolation et color. Les champs permissionMode, hooks, mcpServers et initialPrompt sont ignores : un fichier d'agent ne peut pas ajouter de hooks ni de serveurs MCP tout seul.
Commande — commands/profil-table.md
Une commande est un simple fichier Markdown que tu lances par son nom. C'est l'ancien format ; prefere skills/ pour du neuf. Ici c'est utile pour un geste court et repetitif.
---
description: Genere la requete de profilage d'une table
---
Genere une requete SQL de profilage pour la table indiquee. Elle doit retourner,
en une seule execution : le nombre total de lignes, et pour chaque colonne le nombre
de valeurs nulles, le nombre de valeurs distinctes, le minimum et le maximum
quand le type le permet.
Demande d'abord le moteur SQL cible s'il n'est pas precise : la syntaxe change
entre PostgreSQL, BigQuery, Snowflake et SQL Server.Le fichier commands/profil-table.md devient /boite-a-outils-data:profil-table. Un sous-dossier ajoute un segment : commands/db/migrate.md devient /boite-a-outils-data:db:migrate.
Hook — hooks/hooks.json
Les hooks se rangent dans hooks/hooks.json, sous une cle de premier niveau "hooks", dans la meme forme que l'objet hooks d'un settings.json. Tu peux donc recopier un hook existant sans le reecrire.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/verifie_sql.sh\""
}
]
}
]
}
}Et le script scripts/verifie_sql.sh, a rendre executable avec chmod +x :
#!/bin/bash
# Refuse un SELECT * ecrit dans un fichier .sql du projet.
fichier=$(jq -r '.tool_input.file_path // empty')
case "$fichier" in
*.sql)
if grep -qiE 'select[[:space:]]+\*' "$fichier"; then
echo "Attention : SELECT * detecte dans $fichier" >&2
fi
;;
esac
exit 0Trois points a retenir :
- Les hooks ne t'attendent pas. Un hook de plugin ne se declenche pas seulement quand tu utilises une skill du plugin. Claude Code l'enregistre des que la session charge le plugin, et il se declenche sur son evenement a partir de la. Pour limiter, resserre le
matcher. - Le guillemetage compte. Quand
commandn'a pas deargs, la commande passe par un shell : entoure donc le chemin${CLAUDE_PLUGIN_ROOT}de guillemets doubles. Avecargs, chaque element est passe tel quel, sans shell, et aucun guillemet n'est necessaire. - L'environnement est fourni. Chaque processus de hook recoit
CLAUDE_PLUGIN_ROOTetCLAUDE_PLUGIN_DATA.
Serveur MCP — .mcp.json
Le fichier .mcp.json a la racine du plugin a la meme forme qu'un .mcp.json de projet.
{
"mcpServers": {
"entrepot": {
"command": "python",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp/serveur_entrepot.py"],
"env": {
"ENTREPOT_DSN": "${CLAUDE_PROJECT_DIR}/.env.entrepot"
}
}
}
}Une fois le plugin charge, lance /mcp dans la session : le serveur apparait sous le nom plugin:boite-a-outils-data:entrepot. Ses outils sont nommes mcp__plugin_boite-a-outils-data_entrepot__<outil>.
Dans args, aucun guillemetage n'est necessaire : chaque element est passe comme un argument unique.
4. Valider et tester en local 🧪
Valider
claude plugin validate ./boite-a-outils-dataLa commande verifie le manifeste et le frontmatter de chaque fichier de skill, d'agent et de commande. Elle affiche le chemin du manifeste verifie puis ✔ Validation passed, ou Validation passed with warnings s'il reste des avertissements. En cas d'echec, chaque ligne au-dessus du resultat nomme le champ a corriger.
Ajoute --strict pour echouer aussi sur les avertissements. C'est ce que tu mets en CI :
claude plugin validate --strict ./boite-a-outils-dataLa commande sort avec le code 0 en cas de succes, 1 en cas d'echec, 2 en cas d'erreur inattendue.
Charger pour une session
Pas besoin de marketplace pour tester. Tu charges le dossier directement, puis tu lances /boite-a-outils-data:revue-sql dans la session. Le drapeau est repetable et accepte aussi une archive .zip :
claude --plugin-dir ./boite-a-outils-data --plugin-dir ./autre-plugin.zipUn plugin charge ainsi est un plugin de session uniquement. claude plugin list l'affiche comme <nom>@inline avec le scope session, mais seulement si le meme drapeau precede la sous-commande :
claude --plugin-dir ./boite-a-outils-data plugin listQuand tu modifies un fichier pendant la session, relance /reload-plugins.
Charger a chaque session, sans drapeau
Claude Code charge comme plugin, dans toutes les sessions, tout dossier place sous ~/.claude/skills/ qui contient un .claude-plugin/plugin.json. La commande claude plugin init echafaude un plugin de ce type. Elle demande Claude Code v2.1.157 ou plus recent :
claude plugin init boite-a-outils-data --with skills hooksElle cree ~/.claude/skills/boite-a-outils-data/ avec un .claude-plugin/plugin.json et un SKILL.md a la racine. Elle annonce ensuite que le plugin se chargera sous l'identifiant boite-a-outils-data@skills-dir.
Les valeurs acceptees par --with sont skills, agents, hooks, mcp, lsp, output-style et channel. Tu peux en enchainer plusieurs, comme ci-dessus. La commande n'a aucun drapeau pour ecrire ailleurs que dans ~/.claude/skills/.
Pour arreter de le charger : supprime son dossier, ou lance claude plugin disable boite-a-outils-data@skills-dir.
5. Quand ca ne marche pas 🔧
Deroule ces quatre verifications dans l'ordre :
claude plugin validate <chemin>dans ton shell./reload-pluginsdans la session, pour appliquer tes dernieres editions./plugindans la session, onglets Installed puis Errors.claude plugin listdans ton shell. Il affiche les plugins de session et de dossier de skills avecStatus: ✔ loadedou l'erreur de chargement.
Les pannes les plus frequentes :
| Symptome | Cause | Correction |
|---|---|---|
<composant> path not found: <chemin> dans l'onglet Errors | Un chemin de composant du manifeste pointe vers rien | Corrige le chemin ou cree le dossier, puis /reload-plugins |
| Le plugin se charge mais ses skills manquent | skills/ est dans .claude-plugin/, ou une entree skills pointe vers un fichier | Deplace skills/ a la racine ; chaque entree doit pointer vers un dossier contenant SKILL.md |
--plugin-dir ne charge rien, sans erreur | Tu as pointe la racine d'une marketplace au lieu d'un plugin | Pointe le dossier d'un seul plugin, celui qui contient .claude-plugin/plugin.json |
| Un hook ne se declenche jamais | Le matcher ne correspond pas | Verifie le journal de debug des hooks, qui montre quels hooks ont matche et leurs codes de sortie |
| Un serveur MCP n'apparait pas | Config invalide | Lance /mcp dans la session pour voir son statut |
6. Template a copier-coller 📋
Prompt pour faire echafauder ton plugin par Claude Code.
Contexte : je suis [DEV / DATA ENGINEER / DATA ANALYST] et je travaille avec
[TECHNOS : ex. dbt, Airflow, PostgreSQL, pandas].
Je veux creer un plugin Claude Code nomme [NOM_EN_KEBAB_CASE].
Son but en une phrase : [BUT_DU_PLUGIN].
Il doit contenir :
- skills : [LISTE : nom + ce que la skill doit faire]
- agents : [LISTE : nom + role de l'agent]
- hooks : [LISTE : evenement + ce qui doit se declencher]
- serveurs MCP : [LISTE ou "aucun"]
Fais exactement ceci, dans cet ordre :
1. Donne l'arborescence complete du dossier, en bloc de code text.
2. Ecris .claude-plugin/plugin.json avec uniquement des champs reels du manifeste.
N'invente aucun champ. Ne mets pas de champ de composant si tout est
a son emplacement par defaut.
3. Ecris chaque fichier SKILL.md avec un frontmatter contenant name et description,
la description etant redigee pour declencher au bon moment.
4. Ecris chaque fichier d'agent avec un frontmatter limite aux champs supportes.
5. Ecris hooks/hooks.json si des hooks sont demandes, en entourant de guillemets
doubles tout chemin ${CLAUDE_PLUGIN_ROOT} place dans un champ command sans args.
6. Donne les commandes exactes a lancer pour valider et tester en local.
Contraintes : tout en francais dans les contenus. Chemins avec des barres obliques
vers l'avant uniquement. Aucun chemin sortant de la racine du plugin.A retenir
- Un plugin se cree avec deux choses : un dossier et un
.claude-plugin/plugin.jsondontnameest obligatoire. - Chaque composant a un emplacement par defaut :
skills/,agents/,commands/,hooks/hooks.json,.mcp.json. Tant que tu les respectes, aucun champ de composant n'est necessaire dans le manifeste. skills,hooksetmcpServerss'ajoutent au scan par defaut ;commandsetagentsle remplacent.- Teste avec
claude --plugin-dir ./mon-plugin, sans marketplace, et relance/reload-pluginsapres chaque edition. claude plugin validate --strictavant de partager : il verifie le manifeste et tous les frontmatters, et sort en code 1 sur avertissement.- Un hook de plugin se declenche des que la session charge le plugin, pas seulement quand tu utilises ses skills. Resserre le
matcher.
Sources
- Create a plugin — Claude Code
- Add components to a plugin — Claude Code
- Plugin manifest reference — Claude Code
- Plugin commands reference — Claude Code
- Plugin loading reference — Claude Code
- anthropics/claude-code - code d'exemple sur la creation d'un plugin :
plugins/plugin-dev/, la boite a outils officielle (7 skills, commande/plugin-dev:create-plugin) - anthropics/claude-plugins-official - code d'exemple sur les manifestes : des dizaines de
plugin.jsonreels dansplugins/etexternal_plugins/