Les hooks permettent aux plugins d’exécuter du code en réponse à des événements. Tous les hooks reçoivent un objet événement et le contexte du plugin, et ils sont déclarés au moment de la définition du plugin — il n’y a pas d’enregistrement dynamique à l’exécution.
Cette page couvre les plugins sandboxed (format standard). Les hooks fonctionnent de manière identique dans les plugins natifs ; la seule différence est que les plugins natifs peuvent également enregistrer page:fragments, ce que les plugins sandboxed ne peuvent pas faire.
Signature du Hook
Chaque gestionnaire de hook prend deux arguments :
async (event: EventType, ctx: PluginContext) => ReturnType;
event— données sur ce qui vient de se passer (contenu en cours d’enregistrement, médias téléchargés, transition de cycle de vie, etc.)ctx— lePluginContextavec storage, KV, logging et APIs contrôlées par les capacités
Configuration des Hooks
Un hook peut être déclaré comme un gestionnaire simple ou enveloppé dans un objet de configuration :
Simple
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
},
}, Full config
hooks: {
"content:afterSave": {
priority: 100,
timeout: 5000,
dependencies: ["audit-log"],
errorPolicy: "continue",
handler: async (event, ctx) => {
ctx.log.info("Content saved");
},
},
}, Options de configuration
| Option | Type | Par défaut | Description |
|---|---|---|---|
priority | number | 100 | Ordre d’exécution. Les nombres inférieurs s’exécutent en premier. |
timeout | number | 5000 | Temps d’exécution maximum en millisecondes. |
dependencies | string[] | [] | IDs de plugins qui doivent s’exécuter avant ce hook. |
errorPolicy | "abort" | "continue" | "abort" | S’il faut arrêter le pipeline en cas d’erreur. |
exclusive | boolean | false | Un seul plugin peut être le fournisseur actif. Utilisé pour email:deliver et comment:moderate. |
handler | function | — | La fonction de gestionnaire du hook. Requis. |
Hooks de cycle de vie
S’exécutent pendant l’installation, l’activation, la désactivation et la suppression du plugin.
plugin:install
S’exécute une fois lorsque le plugin est ajouté pour la première fois à un site.
"plugin:install": async (_event, ctx) => {
ctx.log.info("Installing plugin...");
await ctx.kv.set("settings:enabled", true);
await ctx.storage.items.put("default", { name: "Default Item" });
},
Event: {} — Returns: Promise<void>
plugin:activate
S’exécute lorsque le plugin est activé (après l’installation ou lors de la réactivation).
"plugin:activate": async (_event, ctx) => {
ctx.log.info("Plugin activated");
},
Event: {} — Returns: Promise<void>
plugin:deactivate
S’exécute lorsque le plugin est désactivé (mais pas supprimé).
"plugin:deactivate": async (_event, ctx) => {
ctx.log.info("Plugin deactivated");
},
Event: {} — Returns: Promise<void>
plugin:uninstall
S’exécute lorsque le plugin est supprimé d’un site.
"plugin:uninstall": async (event, ctx) => {
ctx.log.info("Uninstalling plugin...");
if (event.deleteData) {
const result = await ctx.storage.items.query({ limit: 1000 });
await ctx.storage.items.deleteMany(result.items.map((i) => i.id));
}
},
Event: { deleteData: boolean } — Returns: Promise<void>
Hooks de contenu
S’exécutent pendant les opérations de création, mise à jour et suppression du contenu du site.
content:beforeSave
S’exécute avant l’enregistrement du contenu. Retournez du contenu modifié ou void pour le laisser inchangé. Lancez une erreur pour annuler.
"content:beforeSave": async (event, ctx) => {
const { content, collection } = event;
if (collection === "posts" && !content.title) {
throw new Error("Posts require a title");
}
if (typeof content.slug === "string") {
content.slug = content.slug.toLowerCase().replace(/\s+/g, "-");
}
return content;
},
Event: { content, collection, isNew } — Returns: contenu modifié ou void.
content:afterSave
S’exécute après l’enregistrement réussi du contenu. Utilisez pour les effets secondaires comme les notifications, le logging ou les synchronisations externes.
"content:afterSave": async (event, ctx) => {
ctx.log.info(`${event.isNew ? "Created" : "Updated"} ${event.collection}/${event.content.id}`);
if (ctx.http) {
await ctx.http.fetch("https://api.example.com/webhook", {
method: "POST",
body: JSON.stringify({ event: "content:save", id: event.content.id }),
});
}
},
Event: { content, collection, isNew } — Returns: Promise<void>
content:beforeDelete
S’exécute avant la suppression du contenu. Retournez false pour annuler ; true ou void l’autorise.
"content:beforeDelete": async (event, ctx) => {
if (event.collection === "pages" && event.id === "home") {
ctx.log.warn("Cannot delete home page");
return false;
}
return true;
},
Event: { id, collection } — Returns: boolean | void
content:afterDelete
S’exécute après la suppression réussie du contenu.
"content:afterDelete": async (event, ctx) => {
await ctx.storage.cache.delete(`${event.collection}:${event.id}`);
},
Event: { id, collection } — Returns: Promise<void>
content:afterPublish
S’exécute après la promotion du contenu de brouillon à publié. Nécessite la capacité content:read.
Event: { content, collection } — Returns: Promise<void>
content:afterUnpublish
S’exécute après le retour du contenu de publié à brouillon. Nécessite la capacité content:read.
Event: { content, collection } — Returns: Promise<void>
Hooks de médias
media:beforeUpload
S’exécute avant le téléchargement d’un fichier. Retournez des métadonnées de fichier modifiées ou lancez une erreur pour annuler.
"media:beforeUpload": async (event, ctx) => {
if (!event.file.type.startsWith("image/")) {
throw new Error("Only images are allowed");
}
if (event.file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
return { ...event.file, name: `${Date.now()}-${event.file.name}` };
},
Event: { file: { name, type, size } } — Returns: fichier modifié ou void
media:afterUpload
S’exécute après le téléchargement réussi d’un fichier.
Event: { media: { id, filename, mimeType, size, url, createdAt } } — Returns: Promise<void>
Hooks de pages publiques
Ceux-ci permettent aux plugins de contribuer aux pages publiques rendues. Les templates doivent s’inscrire en incluant les composants <EmDashHead>, <EmDashBodyStart> et <EmDashBodyEnd> de emdash/ui.
page:metadata
Contribue des métadonnées typées à <head> — balises meta, propriétés OpenGraph, <link> rels autorisés et JSON-LD. Disponible pour les plugins sandboxed et natifs. Le core valide, déduplique et rend les contributions ; les plugins retournent des données structurées, jamais du HTML brut.
"page:metadata": async (event, ctx) => {
if (event.page.kind !== "content") return null;
return {
kind: "jsonld",
id: `schema:${event.page.content?.collection}:${event.page.content?.id}`,
graph: {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: event.page.pageTitle ?? event.page.title,
description: event.page.description,
},
};
},
Event:
{
page: {
url: string;
path: string;
locale: string | null;
kind: "content" | "custom";
pageType: string;
title: string | null;
pageTitle?: string | null;
description: string | null;
canonical: string | null;
image: string | null;
content?: { collection: string; id: string; slug: string | null };
}
}
Returns: PageMetadataContribution | PageMetadataContribution[] | null
Types de contribution :
| Kind | Rend | Clé de déduplication |
|---|---|---|
meta | <meta name="..." content="..."> | key ou name |
property | <meta property="..." content="..."> | key ou property |
link | <link rel="canonical|alternate" href="..."> | canonical: singleton; alternate: key ou hreflang |
jsonld | <script type="application/ld+json"> | id (si présent) |
La première contribution gagne pour toute clé de déduplication. Link rel est restreint à une liste blanche verrouillée par sécurité (canonical, alternate, author, license, nlweb, site.standard.document) ; href doit être HTTP ou HTTPS.
page:fragments
Contribue du HTML brut, des scripts ou des feuilles de style aux points d’insertion de page. Plugins natifs uniquement.
Les plugins sandboxed ne peuvent pas utiliser ce hook car sa sortie s’exécute en tant que code first-party dans le navigateur du visiteur, en dehors de toute limite de sandbox. Pour des contributions de page sécurisées pour sandbox, utilisez page:metadata. Voir Plugins natifs : fragments de page si vous avez besoin de cette surface.
Ordre d’exécution des hooks
Les hooks s’exécutent dans cet ordre :
- Les hooks avec des valeurs de
priorityinférieures s’exécutent en premier. - Pour des priorités égales, les hooks s’exécutent dans l’ordre d’enregistrement du plugin.
- Les hooks avec
dependenciesattendent que ces plugins se terminent.
// Plugin A
"content:afterSave": { priority: 50, handler: async () => {} }
// Plugin B
"content:afterSave": { priority: 100, handler: async () => {} }
// Plugin C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // waits for A even if its priority would normally be later
handler: async () => {},
}
Gestion des erreurs
Lorsqu’un hook lance une erreur ou dépasse le délai :
errorPolicy: "abort"— l’ensemble du pipeline s’arrête et l’opération d’origine peut échouer.errorPolicy: "continue"— l’erreur est enregistrée et les hooks restants s’exécutent toujours.
"content:afterSave": {
timeout: 5000,
errorPolicy: "continue",
handler: async (event, ctx) => {
await ctx.http!.fetch("https://unreliable-api.com/notify");
},
},
Timeouts
Les hooks ont par défaut 5000ms. Augmentez le timeout pour des travaux plus lents :
"content:afterSave": {
timeout: 30000,
handler: async (event, ctx) => {
// Long-running operation
},
},
Référence des hooks
| Hook | Déclencheur | Retour | Exclusif |
|---|---|---|---|
plugin:install | Première installation plugin | void | Non |
plugin:activate | Plugin activé | void | Non |
plugin:deactivate | Plugin désactivé | void | Non |
plugin:uninstall | Plugin supprimé | void | Non |
content:beforeSave | Avant enregistrement contenu | Contenu modifié ou void | Non |
content:afterSave | Après enregistrement contenu | void | Non |
content:beforeDelete | Avant suppression contenu | false pour annuler, sinon autoriser | Non |
content:afterDelete | Après suppression contenu | void | Non |
content:afterPublish | Après publication contenu | void | Non |
content:afterUnpublish | Après dépublication contenu | void | Non |
media:beforeUpload | Avant téléchargement fichier | Info fichier modifiée ou void | Non |
media:afterUpload | Après téléchargement fichier | void | Non |
cron | Tâche planifiée se déclenche | void | Non |
email:beforeSend | Avant envoi email | Message modifié, false ou void | Non |
email:deliver | Livrer email via transport | void | Oui |
email:afterSend | Après envoi email | void | Non |
comment:beforeCreate | Avant stockage commentaire | Événement modifié, false ou void | Non |
comment:moderate | Décider statut commentaire | { status, reason? } | Oui |
comment:afterCreate | Après stockage commentaire | void | Non |
comment:afterModerate | Admin change statut commentaire | void | Non |
page:metadata | Rendu de page | Contributions ou null | Non |
page:fragments | Rendu de page (natif seulement) | Contributions ou null | Non |
Voir la Référence des Hooks pour les types d’événements complets et les signatures des gestionnaires.