Créer un plugin externe — tutoriel

Ce guide parcourt l'authoring d'un plugin distribué en et chargé au runtime par Flexweg CMS — sans rebuild de l'admin.

Ce guide parcourt l'authoring d'un plugin distribué en .zip et chargé au runtime par Flexweg CMS — sans rebuild de l'admin.

Quand écrire un plugin externe (vs in-tree)

Choisissez externe quand :

  • Vous livrez le plugin à plusieurs admins que vous ne contrôlez pas (clients, customers, marketplace public).
  • Vous ne voulez pas rebuild + redéployer l'admin SPA à chaque changement du plugin.
  • Vous voulez que les admins installent / suppriment votre plugin en un clic.

Choisissez in-tree quand :

  • Le plugin n'est livré qu'avec votre propre admin, et un code change là est OK.
  • Vous voulez le typage TypeScript complet contre les sources live de l'admin.
  • Vous n'avez pas besoin d'UX d'install / uninstall.

L'API de hook (api.addFilter, api.addAction, blocs, dashboard cards, pages de réglages) est identique dans les deux. Vous pouvez prototyper en in-tree d'abord et graduer vers externe plus tard — le shape du manifest ne change pas.

Anatomie d'un plugin externe

my-plugin/
├── manifest.json          ← métadonnées d'installation (lues AVANT le bundle)
├── package.json
├── tsconfig.json
├── vite.config.ts
├── scripts/pack.mjs       ← zip dist/ + manifest + README en <id>.zip
├── src/
│   ├── manifest.tsx       ← entry — default-exports un PluginManifest
│   └── types/
│       └── cms-runtime.d.ts ← types pour le @flexweg/cms-runtime externalisé
└── README.md              ← affiché dans la liste des plugins quand un admin clique "Learn more"

L'artefact publié est un .zip unique contenant manifest.json, bundle.js, et README.md optionnel. Toute autre chose dans le ZIP est uploadée sur /admin/plugins/<id>/ sur Flexweg aussi (icônes, sous-images, assets supplémentaires).

manifest.json (métadonnées d'installation)

JSON
{
  "id": "hello-plugin",
  "name": "Hello Plugin",
  "version": "1.0.0",
  "apiVersion": "1.3.0",
  "entry": "bundle.js"
}

L'admin lit ce fichier à l'install + à chaque boot pour décider quoi importer. id est immuable — le changer après install crée un plugin différent.

bundle.js (le payload runtime)

src/manifest.tsx est le point d'entrée. Il doit default-export un objet PluginManifest — même shape que les plugins in-tree :

TSX
import type { PluginManifest } from "@flexweg/cms-runtime";

const manifest: PluginManifest = {
  id: "hello-plugin",
  name: "Hello Plugin",
  version: "1.0.0",
  description: "Ajoute une meta tag et une carte dashboard.",
  author: "Acme Inc.",
  register(api) {
    api.addFilter<string>("page.head.extra", (head) => {
      return head + '<meta name="x-hello-plugin" content="external" />\n';
    });
    api.addAction("publish.complete", (post) => {
      console.log("[hello-plugin] published:", post);
    });
  },
};

export default manifest;

La config Vite critique

Voir Construire un bundle externe pour les détails. Les trois pièces critiques :

  1. define: { "process.env.NODE_ENV": JSON.stringify("production") } — sans ça, le bundle crashe au premier import (React prod shim, react-i18next référencent).
  2. external: ["react", "react/jsx-runtime", "react-dom", "react-dom/client", "react-i18next", "@flexweg/cms-runtime"] — sinon Rollup embarque un deuxième React → "Invalid hook call".
  3. output: { inlineDynamicImports: true } — l'admin charge un seul bundle.js, pas de chunks séparés qui 404.

scripts/pack.mjs

Zippe dist/bundle.js + manifest.json + README.md (si présent) en <id>.zip. Voir Construire un bundle externe.

Build

BASH
npm install --legacy-peer-deps
npm run build
# → hello-plugin.zip

Tester localement

Avant de publier, testez sur votre admin de staging :

  1. Ouvrez /admin/plugins
  2. Cliquez Install plugin
  3. Drag le ZIP dans le modal
  4. L'admin valide le manifest, upload sur Flexweg, dynamic-imports le bundle, et reload

Si la validation échoue (apiVersion hors-range, manifest invalide), l'admin affiche un toast d'erreur explicite. Si le bundle crashe à l'import (Invalid hook call, etc.), regardez les dev tools — l'erreur indique typiquement la cause (deuxième React, deps non-externalisées, etc.).

Une fois publié

Pour faire évoluer le plugin :

  1. Modifiez le code source
  2. Bumpez manifest.json ET package.json (gardez-les synchros)
  3. npm run build
  4. Distribuez le nouveau ZIP

Les utilisateurs installent par-dessus l'ancien — l'admin fait un upgrade in-place : pré-clean l'ancien dossier sur Flexweg, upload le nouveau, replace l'entrée registry. Affiche v1.0.0 → v1.1.0 dans le toast.

Les configs persistées (pluginConfigs[<id>]) sont préservées entre versions — donc vous pouvez ajouter de nouvelles clés à defaultConfig sans casser les utilisateurs existants (les nouvelles clés apparaissent avec leur valeur par défaut jusqu'à modification explicite).

Pour aller plus loin