Riferimento agli hook

In questa pagina

Gli hook permettent aux plugins d’intercepter et de modifier le comportement d’EmDash à des points précis du cycle de vie du contenuto, des médias, des e-mails, des commentos et des pages.

Panoramica degli hook

Le tableau suivant répertorie chaque hook, ce qui le déclenche, ce qu’il peut modifier et s’il est exclusif :

HookTriggerPuò modificareEsclusivo
content:beforeSavePrima del enregistrement du contenutoDati del contenutotoNo
content:afterSaveDopo il enregistrement du contenutoNienteNo
content:beforeDeletePrima della eliminazione du contenutoPuò annullareNo
content:afterDeleteDopo la eliminazione du contenutoNienteNo
content:beforePublishPrima della pubblicazione du contenutoPuò annullareNo
content:beforeSchedulePrima della pianificazione du contenutoPuò annullareNo
content:beforeUnpublishPrima della dépubblicazione du contenutoPuò annullareNo
content:afterPublishDopo la pubblicazione du contenutoNienteNo
content:afterUnpublishDopo la dépubblicazione du contenutoNienteNo
content:afterRestoreDopo la restauration du contenutoNienteNo
content:afterScheduleDopo la pianificazione du contenutoNienteNo
content:afterUnscheduleDopo il annulation de la pianificazioneNienteNo
media:beforeUploadPrima del caricamento d’un fileMetadati del fileNo
media:afterUploadDopo il caricamento d’un fileNienteNo
cronEsecuzione di une attività pianificataNienteNo
email:beforeSendPrima della livraison de l’e-mailMessaggio, può annullareNon
email:deliverConsegna e-mail via le transportNienteSì
email:afterSendAprès une livraison d’e-mail réussieNienteNo
comment:beforeCreatePrima del stockage du commentoCommentaire, peut annulerNon
comment:moderateDecidere lo stato d’approbation du commentoStatoSì
comment:afterCreateDopo il stockage du commentoNienteNo
comment:afterModerateAprès changement de statut du commento par un adminNienteNo
page:metadataRendu du head de pagina pubblicaContribuire tagNo
page:fragmentsRendu du body de pagina pubblicaIniettare scriptNo
plugin:installAlla première installation du pluginNienteNo
plugin:activateQuando il plugin est activéNienteNo
plugin:deactivateQuando il plugin est désactivéNienteNo
plugin:uninstallQuando il plugin est suppriméNienteNo

Hook del contenutoto

content:beforeSave

Capability: content:write

Viene eseguito avant l’enregistrement du contenuto en base de données. Usalo pour valider, transformer ou enrichir le contenuto. 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 salvataggio. 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 salvataggios REST authentifiés, d’édition visuelle et MCP incluent actor. Les écritures internes sans utilisateur authentifié l’omettent.

Valore di ritorno

  • Restituisci l’objet contenuto modifié pour appliquer les changements
  • Restituisci une enveloppe d’erreur de hook sandbox pour rejeter l’enregistrement avec un motif en texte brut borné
  • Restituisci void pour laisser passer inchangé

Un hook en sandbox renvoie cette enveloppe complète pour rejeter un salvataggio :

return {
	__emdashSandboxHookResult: true,
	version: 1,
	error: {
		code: "SAVE_REJECTED",
		reason: "Add a summary before saving.",
	},
};

L’host trim reason et accepte entre 1 et 500 caractères.

content:afterSave

Capability: content:read

Viene eseguito après l’enregistrement du contenuto. Usalo 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.

Valore di ritorno

Non è previsto alcun valore di ritorno.

content:beforeDelete

Capability: content:read

Viene eseguito avant la eliminazione du contenuto. Usalo pour valider ou empêcher la eliminazione.

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 cestino. 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 eliminazione définitive contourne content:beforeDelete, le hook ne peut donc pas empêcher un amministratore de supprimer définitivement une entrée déjà en cestino.

Valore di ritorno

  • Restituisci false pour annuler la eliminazione
  • Restituisci true ou void pour autoriser

content:afterDelete

Capability: content:read

Viene eseguito après la eliminazione du contenuto. Usalo pour le nettoyage.

hooks: {
  "content:afterDelete": async (event, ctx) => {
    const { id, collection, permanent } = event;

    if (permanent) {
      await ctx.storage.relatedItems.delete(`${collection}:${id}`);
    }
  },
}

L’evento contient id, collection et permanent. permanent est false lors du passage à la cestino et true lors d’une eliminazione définitive. Vérifiez-le avant de supprimer des données dont une entrée restaurée aurait encore besoin. Ce hook non ha valore di ritorno.

content:beforePublish

Capability: hooks.content-policy:register

Viene eseguito avant la pubblicazione du contenuto. Restituisci 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.

L’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, pubblicazione ou eliminazione réussie efface l’enregistrement ; un amministratore peut rejeter un salvataggio obsolète.

content:beforeSchedule

Capability: hooks.content-policy:register

Viene eseguito avant qu’une entrée reçoive une heure de pubblicazione. 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 amministratore peut toujours annuler une pubblicazione future.

content:beforeUnpublish

Capability: hooks.content-policy:register

Viene eseguito avant de retirer du contenuto 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 pubblicazione. Décisions invalides et erreurs de politique abort inattendues échouent en mode fermé sans exposer les messages d’exception.

content:afterPublish

Viene eseguito après qu’une entrée est publiée avec succès, y compris une pubblicazione automatique à l’heure planifiée. Usalo 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}`);
  },
}

L’hook requiert la capability content:read. EmDash l’exécute après la réponse de pubblicazione ; sa valeur de retour ne peut ni modifier l’entrée ni annuler la pubblicazione. Gli errori sont journalisées ; avec errorPolicy: "abort", les hooks de pubblicazione suivants ne s’exécutent pas.

content:afterUnpublish

Viene eseguito après une dépubblicazione réussie. Usalo pour retirer ou mettre à jour des copies dans des systèmes externes. La dépubblicazione annule aussi les pianificaziones 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

Viene eseguito après restauration depuis la cestino. Requiert content:read. L’entrée restaurée est un brouillon sans pianificazione, quel que soit son statut avant la cestino. Retirer la pianificazione 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

Viene eseguito après pianificazione d’une pubblicazione future. Requiert content:read.

hooks: {
  "content:afterSchedule": async (event, ctx) => {
    ctx.log.info(`Scheduled ${event.collection}/${event.content.id}`);
  },
}

content:afterUnschedule

Viene eseguito après annulation de la pianificazione du contenuto. 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.

Valore di ritorno

Non è previsto alcun valore di ritorno.

Hook dei media

media:beforeUpload

Capability: media:write

Viene eseguito avant le caricamento d’un file. Usalo pour valider, renommer ou rejeter des files.

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

Valore di ritorno

  • Restituisci des métadonnées de file modifiées
  • Restituisci void pour laisser passer inchangé
  • Lancez une exception pour rejeter le caricamento

media:afterUpload

Capability: media:read

Viene eseguito après le caricamento. Usalo 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;
	};
}

Valore di ritorno

Non è previsto alcun valore di ritorno.

Hook del ciclo di vita

Gli hook de cycle de vie ne requièrent aucune capability d’enregistrement.

plugin:install

Viene eseguito lors de la première installation d’un plugin. Usalo 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

Viene eseguito lorsque le plugin est activé (après installation ou réactivation).

hooks: {
  "plugin:activate": async (event, ctx) => {
    ctx.log.info("Plugin activated");
  },
}

plugin:deactivate

Viene eseguito 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

Viene eseguito lors de la eliminazione d’un plugin. Usalo 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
}

L’hook de désinstallation non ha valore di ritorno.

Hook cron

cron

Capability : Nessuna richiesta

Se déclenche lorsqu’une attività pianificata 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;
}

L’hook cron non ha valore di ritorno.

Hook 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;
}

Valore di ritorno

  • Restituisci le message modifié
  • Restituisci false pour annuler la livraison

email:deliver

Capability: hooks.email-transport:register | Esclusivo: 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 valore di ritorno

interface EmailDeliverEvent {
	message: {
		to: string;
		cc?: string[];
		replyTo?: string;
		subject: string;
		text: string;
		html?: string;
	};
	source: string;
}

L’hook non ha valore di ritorno. 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. Gli errori 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 valore di ritorno

email:afterSend reçoit les mêmes champs message et source que email:deliver. Pas de valeur de retour.

Hook dei commenti

Gli hook de commentos 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 commento, 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 commento. 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>;
}

Valore di ritorno

  • Restituisci l’événement modifié
  • Restituisci false pour rejeter
  • Devuelva void para dejar pasar

comment:moderate

Capability: users:read | Esclusivo: Oui

Décide si un commento 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;
}

Valore di ritorno

{ status: "approved" | "pending" | "spam"; reason?: string }

Moderatore attivo

EmDash inclut un modérateur intégré appliquant les paramètres de commentos 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 commentos 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 commento. Usalo 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 valore di ritorno

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

L’hook non ha valore di ritorno.

comment:afterModerate

Capability: users:read

Viene eseguito après changement de statut par un admin ou un plugin. Une transition réussie exécute le hook une fois. Gli errori 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 };
}

L’hook non ha valore di ritorno.

Hook di pagina

Gli hook 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 pagina pubblica 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 : Nessuna richiesta

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 } },
    ];
  },
}

Tipi di contributo

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.

Restituisci 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";`,
      },
    ];
  },
}

Tipi di contributo

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

Restituisci un fragment, un tableau ou null si le plugin n’ajoute rien.

Configurazione degli hook

Gli hook 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) => { ... },
  },
}

Opzioni di configurazione

OpzioneTipoPar défautDescrizione
prioritynumber100Ordine di esecuzione (più basso = prima)
timeoutnumber5000Tempo massimo di esecuzione in millisecondi
dependenciesstring[][]ID plugin che devono essere eseguiti per primi
errorPolicystring"abort""continue" per ignorare gli errori
exclusivebooleanfalseUn solo plugin può essere il provider attivo (hooks provider comme email:deliver, comment:moderate)

Contesto del plugin

Tutti gli hook ricevono 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;
}

Vedi Capability dei plugin pour la capability requise par chaque API de contexte.

Gestione degli errori

Gli errori negli hook vengono registrati in base a errorPolicy :

  • "abort" (par défaut) — arrêter l’exécution, rollback della transazione se applicabile
  • "continue" — registrare l’errore e continuare con l’hook successivo
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);
      }
    },
  },
}

Ordine di esecuzione

Gli hook vengono eseguiti in questo ordine :

  1. Ordinati per priority (crescente)
  2. I plugin con dependencies vengono eseguiti dopo le loro dipendenze
  3. A parità di priorità, l’ordre è deterministico ma non specificato
// This runs first (priority 10)
{ priority: 10, handler: ... }

// This runs second (priority 50)
{ priority: 50, handler: ... }

// This runs last (default priority 100)
{ handler: ... }