Référence du manifest plugin
Chaque plugin exporte un unique depuis son .
Chaque plugin exporte un PluginManifest<TConfig> unique depuis son manifest.ts.
Shape complète
export interface PluginManifest<TConfig = unknown> {
id: string;
name: string;
version: string;
description?: string;
author?: string;
readme?: string;
register: (api: PluginApi) => void;
settings?: PluginSettingsPageDef<TConfig>;
i18n?: Partial<Record<AdminLocale, Record<string, unknown>>>;
}
Champs
id — obligatoire
L'identifiant unique du plugin. Kebab-case lower ASCII. Utilisé comme :
- Clé dans
settings.enabledPlugins(booléen on/off) - Clé dans
settings.pluginConfigs(config persistée) - Namespace dans
pluginApi.registerBlock(les blocs sont préfixés avec<id>/) - Route de la page de réglages :
/settings/plugin/<id> - Nom du dossier sur Flexweg pour les plugins externes :
/admin/plugins/<id>/ - Namespace i18next :
useTranslation('<id>')
Immuable après installation. Changer l'id casse les références persistées.
name — obligatoire
Nom d'affichage UI. Apparaît dans :
- La carte du plugin sur
/admin/plugins - L'onglet de réglages dans la sidebar Settings
- Le titre de la page de réglages
Peut contenir Unicode. Pas de limite de longueur (mais raisonnable est mieux).
version — obligatoire
Semver du plugin lui-même (ex. 1.2.3). Différent de apiVersion (qui est le version de l'API runtime contre laquelle le plugin est compilé).
Utilisé pour la détection de mise à jour à la réinstallation de plugins externes (le modal install affiche v1.0.0 → v1.1.0).
description — optionnel
Phrase courte. Affichée sur la carte du plugin et sur sa page de réglages.
author — optionnel
Nom de l'auteur ou de l'organisation. Affiché sur la carte.
readme — optionnel
Chaîne markdown — typiquement importée via ?raw :
import readme from "./README.md?raw";
const manifest: PluginManifest = {
// ...
readme,
};
Affichée dans un panneau dépliable sur la carte du plugin (Voir le README).
register — obligatoire
La fonction principale. Appelée par applyPluginRegistration chaque fois que les réglages changent et que ce plugin est activé. Reçoit l'API runtime.
register(api) {
api.addFilter("page.head.extra", myHandler);
api.addAction("publish.complete", async (post, ctx) => { ... });
api.registerBlock(myBlockManifest);
api.registerDashboardCard({ id, priority, component });
api.registerRegenerationTarget({ id, labelKey, run });
}
Ne pas garder d'état entre les appels — tout l'état configurable vit dans pluginConfigs. La fonction tourne plusieurs fois sur la vie de l'app.
settings — optionnel
Déclare une page de réglages.
settings: {
navLabelKey: "title", // clé i18n pour le label de l'onglet
defaultConfig: { ... }, // valeurs par défaut
component: MySettingsPage, // composant React
}
Le composant reçoit { config, save } :
config: le merge{ ...defaultConfig, ...stored }save(next): patchepluginConfigs[<id>] = next
Voir Page de réglages plugin.
i18n — optionnel
Bundles de traduction par locale admin.
import { en, fr, de, es, nl, pt, ko } from "./i18n";
const manifest: PluginManifest = {
// ...
i18n: { en, fr, de, es, nl, pt, ko },
};
Chargés au chargement du module via loadPluginTranslations() dans un namespace i18next du nom de l'id du plugin. Le composant de réglages appelle useTranslation('<id>') pour y accéder.
Bundles : un fichier i18n.ts qui exporte un objet plat par langue :
// i18n.ts
export const en = {
title: "My Plugin",
description: "What it does",
sections: {
general: "General",
},
actions: {
save: "Save",
saved: "Saved",
},
};
export const fr = {
title: "Mon plugin",
description: "Ce que ça fait",
sections: {
general: "Général",
},
actions: {
save: "Sauvegarder",
saved: "Sauvegardé",
},
};
Et ainsi de suite pour de / es / nl / pt / ko. Les clés manquantes dans une langue fallback à EN.
Conventions
- Garder le
register()synchronisé — pas d'awaitau top-level. Les enregistrements doivent tous arriver à la fin deregister(). - Préfixer les hooks customs avec votre id :
addAction("my-plugin.something")— évite les collisions avec d'autres plugins. - Préfixer les blocs avec votre id :
id: "my-plugin/my-block"— pareil, évite les collisions. - Préfixer les cartes :
id: "my-plugin/card". Même rationale.
Exemple complet
import type { PluginManifest } from "@flexweg/cms-runtime";
import { en, fr } from "./i18n";
import { MySettingsPage, DEFAULT_CONFIG } from "./SettingsPage";
import readme from "./README.md?raw";
const manifest: PluginManifest<MyConfig> = {
id: "my-plugin",
name: "My Plugin",
version: "1.0.0",
description: "Adds X to your CMS",
author: "Me",
readme,
i18n: { en, fr },
settings: {
navLabelKey: "title",
defaultConfig: DEFAULT_CONFIG,
component: MySettingsPage,
},
register(api) {
api.addAction("publish.complete", async (post, ctx) => {
const config = ctx.settings.pluginConfigs?.["my-plugin"] ?? DEFAULT_CONFIG;
if (!config.enabled) return;
await doSomething(post, ctx, config);
});
},
};
export default manifest;