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

TS
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 :

TS
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.

TS
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.

TS
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) : patche pluginConfigs[<id>] = next

Voir Page de réglages plugin.

i18n — optionnel

Bundles de traduction par locale admin.

TS
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 :

TS
// 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'await au top-level. Les enregistrements doivent tous arriver à la fin de register().
  • 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

TS
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;