Os hooks permettent aux plugins d’intercepter et de modifier le comportement d’EmDash à des points précis du cycle de vie du conteúdo, des médias, des e-mails, des comentários et des pages.
Visão geral dos hooks
Le tableau suivant répertorie chaque hook, ce qui le déclenche, ce qu’il peut modifier et s’il est exclusif :
| Hook | Gatilho | Pode modificar | Exclusivo |
|---|---|---|---|
content:beforeSave | Avant l’enregistrement du conteúdo | Dados do conteúdo | Não |
content:afterSave | Après l’enregistrement du conteúdo | Nada | Não |
content:beforeDelete | Avant la eliminação du conteúdo | Pode cancelar | Não |
content:afterDelete | Après la eliminação du conteúdo | Nada | Não |
content:beforePublish | Avant la publicação du conteúdo | Pode cancelar | Não |
content:beforeSchedule | Avant la agendamento du conteúdo | Pode cancelar | Não |
content:beforeUnpublish | Avant la dépublicação du conteúdo | Pode cancelar | Não |
content:afterPublish | Après la publicação du conteúdo | Nada | Não |
content:afterUnpublish | Après la dépublicação du conteúdo | Nada | Não |
content:afterRestore | Après la restauration du conteúdo | Nada | Não |
content:afterSchedule | Après la agendamento du conteúdo | Nada | Não |
content:afterUnschedule | Après l’annulation de la agendamento | Nada | Não |
media:beforeUpload | Avant le téléversement d’un fichier | Métadonnées du fichier | Não |
media:afterUpload | Après le téléversement d’un fichier | Nada | Não |
cron | Exécution d’une tâche planifiée | Nada | Não |
email:beforeSend | Avant la livraison de l’e-mail | Message, peut annuler | Non |
email:deliver | Livrer l’e-mail via le transport | Nada | Sim |
email:afterSend | Après une livraison d’e-mail réussie | Nada | Não |
comment:beforeCreate | Avant le stockage du comentário | Commentaire, peut annuler | Non |
comment:moderate | Décider du statut d’approbation du comentário | Statut | Sim |
comment:afterCreate | Après le stockage du comentário | Nada | Não |
comment:afterModerate | Après changement de statut du comentário par un admin | Nada | Não |
page:metadata | Rendu du head de page publique | Contribuer des balises | Não |
page:fragments | Rendu du body de page publique | Injecter des scripts | Não |
plugin:install | Lors de la première installation du plugin | Nada | Não |
plugin:activate | Lorsque le plugin est activé | Nada | Não |
plugin:deactivate | Lorsque le plugin est désactivé | Nada | Não |
plugin:uninstall | Lorsque le plugin est supprimé | Nada | Não |
Hooks de conteúdo
content:beforeSave
Capability: content:write
É executado avant l’enregistrement du conteúdo en base de données. Use-o pour valider, transformer ou enrichir le conteúdo. Un hook en sandbox rejette l’enregistrement en renvoyant un résultat de hook version 1 avec une erreur SAVE_REJECTED et un reason en texte brut de 1 à 500 caractères. L’API répond SAVE_REJECTED et l’admin identifie le plugin et affiche le motif. Des résultats d’erreur vides, trop longs, mal formés ou inconnus font échouer l’enregistrement avec une réponse générique CONTENT_HOOK_ERROR.
Depuis le processus hôte, lancez ContentSaveRejectedError (exporté depuis emdash) pour rejeter un enregistrement. Toute autre exception dans l’un ou l’autre mode d’exécution fait échouer l’enregistrement avec une réponse générique qui n’expose pas le message d’exception.
import { definePlugin } from "emdash";
export default definePlugin({
id: "my-plugin",
version: "1.0.0",
hooks: {
"content:beforeSave": async (event, ctx) => {
const { content, collection, isNew } = event;
// Add timestamps
if (isNew) {
content.createdBy = "system";
}
content.modifiedAt = new Date().toISOString();
// Return modified content
return content;
},
},
});
Evento
interface ActorInfo {
readonly id: string;
readonly role: number;
}
interface ContentHookEvent {
content: Record<string, unknown>; // Content data
collection: string; // Collection slug
isNew: boolean; // True for creates, false for updates
id?: string; // ID of the existing item on updates; absent on creates
actor?: ActorInfo; // Authenticated user that initiated the save
}
Lors d’une mise à jour, content ne contient que les valeurs de champs soumises. Chargez l’entrée stockée avec ctx.content.get(event.collection, event.id) si le hook doit la comparer. Les enregistrements REST authentifiés, d’édition visuelle et MCP incluent actor. Les écritures internes sans utilisateur authentifié l’omettent.
Valor de retorno
- Retorne l’objet conteúdo modifié pour appliquer les changements
- Retorne une enveloppe d’erreur de hook sandbox pour rejeter l’enregistrement avec un motif en texte brut borné
- Retorne
voidpour laisser passer inchangé
Un hook en sandbox renvoie cette enveloppe complète pour rejeter un enregistrement :
return {
__emdashSandboxHookResult: true,
version: 1,
error: {
code: "SAVE_REJECTED",
reason: "Add a summary before saving.",
},
};
L’hôte trim reason et accepte entre 1 et 500 caractères.
content:afterSave
Capability: content:read
É executado après l’enregistrement du conteúdo. Use-o pour des effets de bord tels que notifications, invalidation du cache ou synchronisation externe.
hooks: {
"content:afterSave": async (event, ctx) => {
const { content, collection, isNew } = event;
if (collection === "posts" && content.status === "published") {
// Notify external service
await ctx.http?.fetch("https://api.example.com/notify", {
method: "POST",
body: JSON.stringify({ postId: content.id }),
});
}
},
}
Evento
content:afterSave reçoit content, collection, isNew et l’actor authentifié optionnel. content est l’entrée enregistrée complète, avec son ID en base dans content.id et les champs de collection sous content.data. L’id optionnelle séparée des mises à jour content:beforeSave est absente après l’enregistrement.
Valor de retorno
Nenhum valor de retorno é esperado.
content:beforeDelete
Capability: content:read
É executado avant la eliminação du conteúdo. Use-o pour valider ou empêcher la eliminação.
hooks: {
"content:beforeDelete": async (event, ctx) => {
const { id, collection } = event;
// Prevent deletion of protected content
const item = await ctx.content?.get(collection, id);
if (item?.data.protected) {
return false; // Cancel deletion
}
// Allow deletion
return true;
},
}
Evento
interface ContentDeleteEvent {
id: string; // Entry ID
collection: string; // Collection slug
permanent?: false; // Present for native plugins; omitted in the sandbox runtime
}
content:beforeDelete ne s’exécute que lorsqu’une entrée est déplacée vers la lixeira. Les plugins natifs reçoivent permanent: false ; le runtime sandbox n’envoie que id et collection. Ne branchez pas dans un hook sandbox sur ce champ. La eliminação définitive contourne content:beforeDelete, le hook ne peut donc pas empêcher un administrador de supprimer définitivement une entrée déjà en lixeira.
Valor de retorno
- Retorne
falsepour annuler la eliminação - Retorne
trueouvoidpour autoriser
content:afterDelete
Capability: content:read
É executado après la eliminação du conteúdo. Use-o pour le nettoyage.
hooks: {
"content:afterDelete": async (event, ctx) => {
const { id, collection, permanent } = event;
if (permanent) {
await ctx.storage.relatedItems.delete(`${collection}:${id}`);
}
},
}
O evento contient id, collection et permanent. permanent est false lors du passage à la lixeira et true lors d’une eliminação définitive. Vérifiez-le avant de supprimer des données dont une entrée restaurée aurait encore besoin. Ce hook não tem valor de retorno.
content:beforePublish
Capability: hooks.content-policy:register
É executado avant la publicação du conteúdo. Retorne void pour autoriser ou { cancel: true, reason } pour rejeter. EmDash accepte 1 à 500 caractères en texte brut dans reason et renvoie PUBLISH_REJECTED en cas d’annulation explicite.
O evento contient le content, collection et origin actuels. Les actions API, MCP et éditeur visuel authentifiées incluent aussi actor avec la source correspondante. L’origine visual-editor requiert le jeton d’action signé et éphémère d’un rendu de barre d’outils authentifié. Les entrées planifiées réexécutent ce hook à l’échéance. Un rejet du planificateur déplanifie et liste l’entrée et le motif dans le tableau de bord. Planification, publicação ou eliminação réussie efface l’enregistrement ; un administrador peut rejeter un enregistrement obsolète.
content:beforeSchedule
Capability: hooks.content-policy:register
É executado avant qu’une entrée reçoive une heure de publicação. Même format de décision et champs d’origine que content:beforePublish, ajoute scheduledAt à l’événement et renvoie SCHEDULE_REJECTED en cas d’annulation explicite.
Il n’existe pas de hook content:beforeUnschedule. Un administrador peut toujours annuler une publicação future.
content:beforeUnpublish
Capability: hooks.content-policy:register
É executado avant de retirer du conteúdo publié en direct. Même format de décision et champs d’origine que content:beforePublish, renvoie UNPUBLISH_REJECTED en cas d’annulation explicite.
hooks.content-policy:register n’implique pas content:read, content:write ni les actions de publicação. Décisions invalides et erreurs de politique abort inattendues échouent en mode fermé sans exposer les messages d’exception.
content:afterPublish
É executado après qu’une entrée est publiée avec succès, y compris une publicação automatique à l’heure planifiée. Use-o pour un travail qui dépend de l’entrée publiée, comme notifier un service ou rafraîchir un index externe.
hooks: {
"content:afterPublish": async (event, ctx) => {
ctx.log.info(`Published ${event.collection}/${event.content.id}`);
},
}
O hook requiert la capability content:read. EmDash l’exécute après la réponse de publicação ; sa valeur de retour ne peut ni modifier l’entrée ni annuler la publicação. Os erros sont journalisées ; avec errorPolicy: "abort", les hooks de publicação suivants ne s’exécutent pas.
content:afterUnpublish
É executado après une dépublicação réussie. Use-o pour retirer ou mettre à jour des copies dans des systèmes externes. La dépublicação annule aussi les agendamentos en attente sans exécuter content:afterUnschedule.
hooks: {
"content:afterUnpublish": async (event, ctx) => {
ctx.log.info(`Unpublished ${event.collection}/${event.content.id}`);
},
}
Ce hook a les mêmes exigences content:read, exécution différée, forme d’événement et contrat sans valeur de retour que content:afterPublish.
content:afterRestore
É executado après restauration depuis la lixeira. Requiert content:read. L’entrée restaurée est un brouillon sans agendamento, quel que soit son statut avant la lixeira. Retirer la agendamento n’exécute pas non plus content:afterUnschedule.
hooks: {
"content:afterRestore": async (event, ctx) => {
ctx.log.info(`Restored ${event.collection}/${event.content.id}`);
},
}
content:afterSchedule
É executado après agendamento d’une publicação future. Requiert content:read.
hooks: {
"content:afterSchedule": async (event, ctx) => {
ctx.log.info(`Scheduled ${event.collection}/${event.content.id}`);
},
}
content:afterUnschedule
É executado après annulation de la agendamento du conteúdo. Requiert content:read.
hooks: {
"content:afterUnschedule": async (event, ctx) => {
ctx.log.info(`Unscheduled ${event.collection}/${event.content.id}`);
},
}
Evento
interface ContentStateChangeEvent {
content: Record<string, unknown>;
collection: string;
}
Cette forme d’événement est partagée par content:afterPublish, content:afterUnpublish, content:afterRestore, content:afterSchedule et content:afterUnschedule. content est l’entrée complète après le changement d’état, avec id, slug et statut ; les champs de collection sont sous content.data.
Valor de retorno
Nenhum valor de retorno é esperado.
Hooks de mídia
media:beforeUpload
Capability: media:write
É executado avant le téléversement d’un fichier. Use-o pour valider, renommer ou rejeter des fichiers.
hooks: {
"media:beforeUpload": async (event, ctx) => {
const { file } = event;
// Reject files over 10MB
if (file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
// Rename file
return {
name: `${Date.now()}-${file.name}`,
type: file.type,
size: file.size,
};
},
}
Evento
interface MediaUploadEvent {
file: {
name: string; // Original filename
type: string; // MIME type
size: number; // Size in bytes
};
}
Valor de retorno
- Retorne des métadonnées de fichier modifiées
- Retorne
voidpour laisser passer inchangé - Lancez une exception pour rejeter le téléversement
media:afterUpload
Capability: media:read
É executado après le téléversement. Use-o pour traitement, vignettes ou extraction de métadonnées.
hooks: {
"media:afterUpload": async (event, ctx) => {
const { media } = event;
if (media.mimeType.startsWith("image/")) {
// Store image metadata
await ctx.kv.set(`media:${media.id}:analyzed`, {
processedAt: new Date().toISOString(),
});
}
},
}
Evento
interface MediaAfterUploadEvent {
media: {
id: string;
filename: string;
mimeType: string;
size: number | null;
url: string;
createdAt: string;
};
}
Valor de retorno
Nenhum valor de retorno é esperado.
Hooks de ciclo de vida
Os hooks de cycle de vie ne requièrent aucune capability d’enregistrement.
plugin:install
É executado lors de la première installation d’un plugin. Use-o pour la configuration initiale, créer des collections de stockage ou des données de seed.
hooks: {
"plugin:install": async (event, ctx) => {
// Initialize default settings
await ctx.settings.set("enabled", true);
await ctx.settings.set("threshold", 100);
ctx.log.info("Plugin installed successfully");
},
}
plugin:activate
É executado lorsque le plugin est activé (après installation ou réactivation).
hooks: {
"plugin:activate": async (event, ctx) => {
ctx.log.info("Plugin activated");
},
}
plugin:deactivate
É executado lorsque le plugin est désactivé.
hooks: {
"plugin:deactivate": async (event, ctx) => {
ctx.log.info("Plugin deactivated");
},
}
plugin:install, plugin:activate et plugin:deactivate reçoivent un objet événement vide. Pas de valeur de retour.
plugin:uninstall
É executado lors de la eliminação d’un plugin. Use-o pour le nettoyage.
hooks: {
"plugin:uninstall": async (event, ctx) => {
const { deleteData } = event;
if (deleteData) {
// Clean up all plugin data
const items = await ctx.settings.list();
for (const { key } of items) {
await ctx.kv.delete(key);
}
}
ctx.log.info("Plugin uninstalled");
},
}
Evento
interface UninstallEvent {
deleteData: boolean; // User chose to delete data
}
O hook de désinstallation não tem valor de retorno.
Hook cron
cron
Capability : Nenhuma necessária
Se déclenche lorsqu’une tâche planifiée s’exécute. Planifiez avec ctx.cron.schedule(). Les expressions cron sont évaluées en UTC sur chaque hôte.
hooks: {
"cron": async (event, ctx) => {
if (event.name === "daily-sync") {
const data = await ctx.http?.fetch("https://api.example.com/data");
ctx.log.info("Sync complete");
}
},
}
Evento
interface CronEvent {
name: string;
data?: Record<string, unknown>;
scheduledAt: string;
}
O hook cron não tem valor de retorno.
Hooks de e-mail
Pour les messages envoyés par les plugins, les hooks e-mail s’exécutent dans l’ordre : email:beforeSend, puis email:deliver, puis email:afterSend. Les messages d’authentification système vont directement à email:deliver ; ils ne passent pas par email:beforeSend ni email:afterSend.
email:beforeSend
Capability: hooks.email-events:register
Hook middleware avant la livraison. Transformez les messages ou annulez la livraison.
hooks: {
"email:beforeSend": async (event, ctx) => {
// Add footer to all emails
return {
...event.message,
text: event.message.text + "\n\n—Sent from My Site",
};
// Or return false to cancel delivery
},
}
Evento
interface EmailBeforeSendEvent {
message: {
to: string;
cc?: string[];
replyTo?: string;
subject: string;
text: string;
html?: string;
};
source: string;
}
Valor de retorno
- Retorne le message modifié
- Retorne
falsepour annuler la livraison
email:deliver
Capability: hooks.email-transport:register | Exclusivo: Oui
Fournisseur de transport. Un seul plugin peut livrer les e-mails. Responsable de l’envoi via un service e-mail.
hooks: {
"email:deliver": {
exclusive: true,
handler: async (event, ctx) => {
await sendViaSES(event.message);
},
},
}
Evento e valor de retorno
interface EmailDeliverEvent {
message: {
to: string;
cc?: string[];
replyTo?: string;
subject: string;
text: string;
html?: string;
};
source: string;
}
O hook não tem valor de retorno. source es "system" para mensajes de autenticación de EmDash y el ID del plugin para mensajes enviados por un plugin.
Les plugins peuvent définir cc et replyTo. Les fournisseurs doivent livrer les deux s’ils sont présents ; ignorer cc prive ces destinataires du message.
email:afterSend
Capability: hooks.email-events:register
Hook fire-and-forget après livraison réussie. Os erros sont journalisées sans propagation.
hooks: {
"email:afterSend": async (event, ctx) => {
await ctx.kv.set(`email:log:${Date.now()}`, {
to: event.message.to,
subject: event.message.subject,
});
},
}
Evento e valor de retorno
email:afterSend reçoit les mêmes champs message et source que email:deliver. Pas de valeur de retour.
Hooks de comentários
Os hooks de comentários s’exécutent dans l’ordre : comment:beforeCreate, puis comment:moderate, puis comment:afterCreate. comment:afterModerate s’exécute séparément lors d’un changement de statut par un admin ou un plugin autorisé.
Après stockage d’un comentário, les hooks reçoivent cette forme d’enregistrement :
interface StoredComment {
id: string;
collection: string;
contentId: string;
parentId: string | null;
authorName: string;
authorEmail: string;
authorUserId: string | null;
body: string;
status: string;
moderationMetadata: Record<string, unknown> | null;
createdAt: string;
updatedAt: string;
}
comment:beforeCreate
Capability: users:read
Hook middleware avant stockage d’un comentário. Enrichissez, validez ou rejetez.
hooks: {
"comment:beforeCreate": async (event, ctx) => {
// Reject comments with links
if (event.comment.body.includes("http")) {
return false;
}
},
}
Evento
interface CommentBeforeCreateEvent {
comment: {
collection: string;
contentId: string;
parentId: string | null;
authorName: string;
authorEmail: string;
authorUserId: string | null;
body: string;
ipHash: string | null;
userAgent: string | null;
};
metadata: Record<string, unknown>;
}
Valor de retorno
- Retorne l’événement modifié
- Retorne
falsepour rejeter - Devuelva
voidpara dejar pasar
comment:moderate
Capability: users:read | Exclusivo: Oui
Décide si un comentário est approuvé, en attente ou spam. Un seul modérateur actif.
hooks: {
"comment:moderate": {
exclusive: true,
handler: async (event, ctx) => {
const score = await checkSpam(event.comment);
return {
status: score > 0.8 ? "spam" : score > 0.5 ? "pending" : "approved",
reason: `Spam score: ${score}`,
};
},
},
}
Evento
interface CommentModerateEvent {
comment: { /* same as beforeCreate */ };
metadata: Record<string, unknown>;
collectionSettings: {
commentsEnabled: boolean;
commentsModeration: "all" | "first_time" | "none";
commentsClosedAfterDays: number;
commentsAutoApproveUsers: boolean;
};
priorApprovedCount: number;
}
Valor de retorno
{ status: "approved" | "pending" | "spam"; reason?: string }
Moderador ativo
EmDash inclut un modérateur intégré appliquant les paramètres de comentários de la collection. Si un seul plugin fournit comment:moderate, EmDash le choisit à la place du modérateur intégré et mémorise le choix. Si plusieurs plugins le fournissent sans choix mémorisé, aucun n’est sélectionné et les nouveaux comentários attendent une revue. Un choix mémorisé, y compris le modérateur intégré, reste tant que le modérateur choisi est activé.
comment:afterCreate
Capability: users:read
Hook fire-and-forget après stockage d’un comentário. Use-o pour les notifications. L’envoi d’e-mail requiert aussi email:send et un fournisseur email:deliver ; sans les deux, ctx.email est undefined.
hooks: {
"comment:afterCreate": async (event, ctx) => {
const recipient = event.contentAuthor?.email;
if (event.comment.status === "approved" && recipient && ctx.email) {
await ctx.email.send({
to: recipient,
subject: `New comment on "${event.content.title}"`,
text: `${event.comment.authorName} commented: ${event.comment.body}`,
});
}
},
}
Evento e valor de retorno
interface CommentAfterCreateEvent {
comment: StoredComment;
metadata: Record<string, unknown>;
content: { id: string; collection: string; slug: string; title?: string };
contentAuthor?: { id: string; name: string | null; email: string };
}
O hook não tem valor de retorno.
comment:afterModerate
Capability: users:read
É executado après changement de statut par un admin ou un plugin. Une transition réussie exécute le hook une fois. Os erros sont journalisées et n’annulent pas le changement de statut.
Evento
interface CommentAfterModerateEvent {
comment: StoredComment;
previousStatus: string;
newStatus: string;
moderator: { id: string; name: string | null };
origin?:
| { source: "admin"; userId: string }
| { source: "plugin"; pluginId: string };
}
O hook não tem valor de retorno.
Hooks de página
Os hooks de page s’exécutent lors du rendu des pages publiques. Ils permettent d’injecter métadonnées et scripts.
Les deux hooks de page reçoivent le contexte de page publique actuel :
interface PublicPageContext {
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 };
seo?: {
ogTitle?: string | null;
ogDescription?: string | null;
ogImage?: string | null;
robots?: string | null;
};
articleMeta?: {
publishedTime?: string | null;
modifiedTime?: string | null;
author?: string | null;
};
siteName?: string;
breadcrumbs?: Array<{ name: string; url: string }>;
siteUrl?: string;
}
interface PageMetadataEvent { page: PublicPageContext }
interface PageFragmentEvent { page: PublicPageContext }
page:metadata
Capability : Nenhuma necessária
Contribuez des balises meta, Open Graph, JSON-LD ou link tags dans le head.
hooks: {
"page:metadata": async (event, ctx) => {
return [
{ kind: "meta", name: "generator", content: "EmDash" },
{ kind: "property", property: "og:site_name", content: event.page.siteName ?? "My Site" },
{ kind: "jsonld", graph: { "@type": "WebSite", name: event.page.siteName } },
];
},
}
Tipos de contribuição
type PageMetadataContribution =
| { kind: "meta"; name: string; content: string; key?: string }
| { kind: "property"; property: string; content: string; key?: string }
| {
kind: "link";
rel: "canonical" | "alternate" | "author" | "license" | "nlweb" | "site.standard.document";
href: string;
hreflang?: string;
key?: string;
}
| {
kind: "jsonld";
id?: string;
graph: Record<string, unknown> | Array<Record<string, unknown>>;
};
Le champ key déduplique — seule la dernière contribution avec une clé donnée est utilisée.
Retorne une contribution, un tableau ou null si le plugin n’ajoute rien.
page:fragments
Capability: hooks.page-fragments:register
Injectez scripts ou HTML dans les pages. Réservé aux plugins natifs.
hooks: {
"page:fragments": async (event, ctx) => {
return [
{
kind: "external-script",
placement: "body:end",
src: "https://analytics.example.com/script.js",
async: true,
},
{
kind: "inline-script",
placement: "head",
code: `window.siteId = "abc123";`,
},
];
},
}
Tipos de contribuição
type PageFragmentContribution =
| {
kind: "external-script";
placement: "head" | "body:start" | "body:end";
src: string;
async?: boolean;
defer?: boolean;
attributes?: Record<string, string>;
key?: string;
}
| {
kind: "inline-script";
placement: "head" | "body:start" | "body:end";
code: string;
attributes?: Record<string, string>;
key?: string;
}
| {
kind: "html";
placement: "head" | "body:start" | "body:end";
html: string;
key?: string;
};
Retorne un fragment, un tableau ou null si le plugin n’ajoute rien.
Configuração de hooks
Os hooks acceptent une fonction handler ou un objet de configuration :
hooks: {
// Simple handler
"content:afterSave": async (event, ctx) => { ... },
// With configuration
"content:beforeSave": {
priority: 50, // Lower runs first (default: 100)
timeout: 10000, // Max execution time in ms (default: 5000)
dependencies: [], // Run after these plugins
errorPolicy: "abort", // "continue" or "abort" (default)
handler: async (event, ctx) => { ... },
},
}
Opções de configuração
| Option | Type | Par défaut | Description |
|---|---|---|---|
priority | number | 100 | Ordem de execução (plus bas = plus tôt) |
timeout | number | 5000 | Durée max d’exécution en millisecondes |
dependencies | string[] | [] | IDs de plugins devant s’exécuter en premier |
errorPolicy | string | "abort" | "continue" pour ignorer les erreurs |
exclusive | boolean | false | Un seul plugin peut être le fournisseur actif (hooks provider comme email:deliver, comment:moderate) |
Contexto do plugin
Tous les hooks reçoivent un objet de contexte avec accès aux API plugin :
interface PluginContext {
plugin: { id: string; version: string };
storage: PluginStorage;
kv: KVAccess;
content?: ContentAccess;
media?: MediaAccess;
http?: HttpAccess;
log: LogAccess;
site: { name: string; url: string; locale: string };
url(path: string): string;
users?: UserAccess;
cron?: CronAccess;
email?: EmailAccess;
}
Consulte Capacidades de plugins pour la capability requise par chaque API de contexte.
Tratamento de erros
Os erros dans les hooks sont journalisées selon errorPolicy :
"abort"(par défaut) — arrêter l’exécution, rollback de transaction si applicable"continue"— journaliser et continuer au hook suivant
hooks: {
"content:beforeSave": {
errorPolicy: "continue", // Don't block save if this fails
handler: async (event, ctx) => {
try {
await ctx.http?.fetch("https://api.example.com/validate");
} catch (error) {
ctx.log.warn("Validation service unavailable", error);
}
},
},
}
Ordem de execução
Os hooks s’exécutent dans cet ordre :
- Triés par
priority(croissant) - Les plugins avec
dependenciess’exécutent après leurs dépendances - À priorité égale, l’ordre est déterministe mais non spécifié
// This runs first (priority 10)
{ priority: 10, handler: ... }
// This runs second (priority 50)
{ priority: 50, handler: ... }
// This runs last (default priority 100)
{ handler: ... }