Gli hook consentono ai plugin di eseguire codice in risposta a eventi. Tutti gli hook ricevono un oggetto evento e il contesto del plugin. Gli hook sono dichiarati alla definizione del plugin, non registrati dinamicamente a runtime.
Firma degli hook
Ogni gestore di hook riceve due argomenti:
async (event: EventType, ctx: PluginContext) => ReturnType;
event— Dati sull’evento (contenuto in salvataggio, media caricato, ecc.)ctx— Il contesto del plugin con storage, KV, logging e API governate dalle capabilities
Configurazione degli hook
Gli hook possono essere dichiarati come semplice gestore o con configurazione completa:
Semplice
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
}
} Configurazione completa
hooks: {
"content:afterSave": {
priority: 100,
timeout: 5000,
dependencies: ["audit-log"],
errorPolicy: "continue",
handler: async (event, ctx) => {
ctx.log.info("Content saved");
}
}
} Opzioni di configurazione
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
priority | number | 100 | Ordine di esecuzione. I numeri più bassi vengono eseguiti per primi. |
timeout | number | 5000 | Tempo massimo di esecuzione in millisecondi. |
dependencies | string[] | [] | ID di plugin che devono essere eseguiti prima di questo hook. |
errorPolicy | "abort" | "continue" | "abort" | Se interrompere la pipeline in caso di errore. |
exclusive | boolean | false | Un solo plugin può essere il provider attivo. Usato per email:deliver e comment:moderate. |
handler | function | — | Funzione gestore dell’hook. Obbligatoria. |
Hook del ciclo di vita
Gli hook del ciclo di vita vengono eseguiti durante installazione, attivazione e disattivazione del plugin.
plugin:install
Viene eseguito una volta quando il plugin viene aggiunto per la prima volta a un sito.
"plugin:install": async (_event, ctx) => {
ctx.log.info("Installing plugin...");
// Seed default data
await ctx.kv.set("settings:enabled", true);
await ctx.storage.items!.put("default", { name: "Default Item" });
}
Evento: {}
Restituisce: Promise<void>
plugin:activate
Viene eseguito quando il plugin è abilitato (dopo l’installazione o quando viene riabilitato).
"plugin:activate": async (_event, ctx) => {
ctx.log.info("Plugin activated");
}
Evento: {}
Restituisce: Promise<void>
plugin:deactivate
Viene eseguito quando il plugin è disabilitato (ma non rimosso).
"plugin:deactivate": async (_event, ctx) => {
ctx.log.info("Plugin deactivated");
// Release resources, pause background work
}
Evento: {}
Restituisce: Promise<void>
plugin:uninstall
Viene eseguito quando il plugin viene rimosso da un sito.
"plugin:uninstall": async (event, ctx) => {
ctx.log.info("Uninstalling plugin...");
if (event.deleteData) {
// User opted to delete plugin data
const result = await ctx.storage.items!.query({ limit: 1000 });
await ctx.storage.items!.deleteMany(result.items.map(i => i.id));
}
}
Evento: { deleteData: boolean }
Restituisce: Promise<void>
Hook sui contenuti
Gli hook sui contenuti vengono eseguiti durante creazione, aggiornamento ed eliminazione.
content:beforeSave
Viene eseguito prima del salvataggio del contenuto. Restituisci il contenuto modificato o void per lasciarlo invariato. Lancia un’eccezione per annullare il salvataggio.
"content:beforeSave": async (event, ctx) => {
const { content, collection, isNew } = event;
// Validate
if (collection === "posts" && !content.title) {
throw new Error("Posts require a title");
}
// Transform
if (content.slug) {
content.slug = content.slug.toLowerCase().replace(/\s+/g, "-");
}
return content;
}
Evento:
{
content: Record<string, unknown>; // Content data being saved
collection: string; // Collection name
isNew: boolean; // True if creating, false if updating
}
Restituisce: Promise<Record<string, unknown> | void>
content:afterSave
Viene eseguito dopo il salvataggio riuscito del contenuto. Usalo per effetti collaterali come notifiche, logging o sincronizzazione con sistemi esterni.
"content:afterSave": async (event, ctx) => {
const { content, collection, isNew } = event;
ctx.log.info(`${isNew ? "Created" : "Updated"} ${collection}/${content.id}`);
// Trigger external sync
if (ctx.http) {
await ctx.http.fetch("https://api.example.com/webhook", {
method: "POST",
body: JSON.stringify({ event: "content:save", id: content.id })
});
}
}
Evento:
{
content: Record<string, unknown>; // Saved content (includes id, timestamps)
collection: string;
isNew: boolean;
}
Restituisce: Promise<void>
content:beforeDelete
Viene eseguito prima dell’eliminazione del contenuto. Restituisci false per annullare l’eliminazione, true o void per consentirla.
"content:beforeDelete": async (event, ctx) => {
const { id, collection } = event;
// Prevent deletion of protected content
if (collection === "pages" && id === "home") {
ctx.log.warn("Cannot delete home page");
return false;
}
return true;
}
Evento:
{
id: string; // Content ID being deleted
collection: string;
}
Restituisce: Promise<boolean | void>
content:afterDelete
Viene eseguito dopo l’eliminazione riuscita del contenuto.
"content:afterDelete": async (event, ctx) => {
const { id, collection } = event;
ctx.log.info(`Deleted ${collection}/${id}`);
// Clean up related plugin data
await ctx.storage.cache!.delete(`${collection}:${id}`);
}
Evento:
{
id: string;
collection: string;
}
Restituisce: Promise<void>
content:afterPublish
Viene eseguito dopo la pubblicazione del contenuto (promosso da bozza a live). Usalo per effetti collaterali come invalidazione della cache, notifiche o sincronizzazione con sistemi esterni.
Richiede la capability read:content.
"content:afterPublish": async (event, ctx) => {
const { content, collection } = event;
ctx.log.info(`Published ${collection}/${content.id}`);
// Notify external system
if (ctx.http) {
await ctx.http.fetch("https://api.example.com/webhook", {
method: "POST",
body: JSON.stringify({ event: "content:publish", id: content.id })
});
}
}
Evento:
{
content: Record<string, unknown>; // Published content (includes id, timestamps)
collection: string;
}
Restituisce: Promise<void>
content:afterUnpublish
Viene eseguito dopo la depubblicazione del contenuto (da live a bozza). Usalo per effetti collaterali come invalidazione della cache o notifica a sistemi esterni.
Richiede la capability read:content.
"content:afterUnpublish": async (event, ctx) => {
const { content, collection } = event;
ctx.log.info(`Unpublished ${collection}/${content.id}`);
}
Evento:
{
content: Record<string, unknown>; // Unpublished content
collection: string;
}
Restituisce: Promise<void>
Hook sui media
Gli hook sui media vengono eseguiti durante il caricamento dei file.
media:beforeUpload
Viene eseguito prima del caricamento di un file. Restituisci le informazioni sul file modificate o void per lasciarle invariate. Lancia un’eccezione per annullare il caricamento.
"media:beforeUpload": async (event, ctx) => {
const { file } = event;
// Validate file type
if (!file.type.startsWith("image/")) {
throw new Error("Only images are allowed");
}
// Validate file size (10MB max)
if (file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
// Rename file
return {
...file,
name: `${Date.now()}-${file.name}`
};
}
Evento:
{
file: {
name: string; // Original filename
type: string; // MIME type
size: number; // Size in bytes
}
}
Restituisce: Promise<{ name: string; type: string; size: number } | void>
media:afterUpload
Viene eseguito dopo il caricamento riuscito di un file.
"media:afterUpload": async (event, ctx) => {
const { media } = event;
ctx.log.info(`Uploaded ${media.filename}`, {
id: media.id,
size: media.size,
mimeType: media.mimeType
});
}
Evento:
{
media: {
id: string;
filename: string;
mimeType: string;
size: number | null;
url: string;
createdAt: string;
}
}
Restituisce: Promise<void>
Ordine di esecuzione degli hook
Gli hook vengono eseguiti in questo ordine:
- Gli hook con valori
prioritypiù bassi vengono eseguiti per primi - A parità di priorità, gli hook vengono eseguiti nell’ordine di registrazione dei plugin
- Gli hook con
dependenciesattendono il completamento di quei plugin
// Plugin A
"content:afterSave": {
priority: 50, // Runs first
handler: async () => {}
}
// Plugin B
"content:afterSave": {
priority: 100, // Runs second (default priority)
handler: async () => {}
}
// Plugin C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // Runs after A, even if priority was lower
handler: async () => {}
}
Gestione degli errori
Quando un hook lancia un’eccezione o va in timeout:
errorPolicy: "abort"— L’intera pipeline si ferma. L’operazione originale può fallire.errorPolicy: "continue"— L’errore viene registrato e gli hook rimanenti vengono comunque eseguiti.
"content:afterSave": {
timeout: 5000,
errorPolicy: "continue", // Don't fail the save if this hook fails
handler: async (event, ctx) => {
// External API call that might fail
await ctx.http!.fetch("https://unreliable-api.com/notify");
}
}
Timeout
Gli hook hanno un timeout predefinito di 5000 ms (5 secondi). Aumentalo per operazioni che possono richiedere più tempo:
"content:afterSave": {
timeout: 30000, // 30 seconds
handler: async (event, ctx) => {
// Long-running operation
}
}
Hook delle pagine pubbliche
Gli hook delle pagine pubbliche consentono ai plugin di contribuire a <head> e <body> delle pagine renderizzate. I template aderiscono usando i componenti <EmDashHead>, <EmDashBodyStart> e <EmDashBodyEnd> da emdash/ui.
page:metadata
Contribuisce metadati tipizzati a <head> — meta tag, proprietà OpenGraph, link canonical/alternate e dati strutturati JSON-LD. Funziona sia in modalità trusted che sandbox.
Il core convalida, deduplica e renderizza i contributi. I plugin restituiscono dati strutturati, mai HTML grezzo.
"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,
},
};
}
Evento:
{
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 };
}
}
Restituisce: PageMetadataContribution | PageMetadataContribution[] | null
Tipi di contributo:
| Kind | Output | Chiave deduplicazione |
|---|---|---|
meta | <meta name="..." content="..."> | key or name |
property | <meta property="..." content="..."> | key or property |
link | <link rel="canonical|alternate" href="..."> | canonical: singleton; alternate: key or hreflang |
jsonld | <script type="application/ld+json"> | id (if present) |
Per ogni chiave di deduplicazione vince il primo contributo. Gli href dei link devono essere HTTP o HTTPS.
page:fragments
Contribuisce HTML grezzo, script o markup ai punti di inserimento della pagina. Solo plugin trusted — i plugin in sandbox non possono usare questo hook.
"page:fragments": async (event, ctx) => {
return {
kind: "external-script",
placement: "head",
src: "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX",
async: true,
};
}
Restituisce: PageFragmentContribution | PageFragmentContribution[] | null
Posizionamenti: "head", "body:start", "body:end". I template che omettono un componente per un posizionamento ignorano silenziosamente i contributi che lo usano.
Riferimento agli hook
| Hook | Attivazione | Valore restituito | Esclusivo |
|---|---|---|---|
plugin:install | Prima installazione del plugin | void | No |
plugin:activate | Plugin abilitato | void | No |
plugin:deactivate | Plugin disabilitato | void | No |
plugin:uninstall | Plugin rimosso | void | No |
content:beforeSave | Prima del salvataggio del contenuto | Contenuto modificato o void | No |
content:afterSave | Dopo il salvataggio del contenuto | void | No |
content:beforeDelete | Prima dell’eliminazione del contenuto | false per annullare, altrimenti consenti | No |
content:afterDelete | Dopo l’eliminazione del contenuto | void | No |
content:afterPublish | Dopo la pubblicazione del contenuto | void | No |
content:afterUnpublish | Dopo la depubblicazione del contenuto | void | No |
media:beforeUpload | Prima del caricamento del file | Info file modificate o void | No |
media:afterUpload | Dopo il caricamento del file | void | No |
cron | Esecuzione di un’attività pianificata | void | No |
email:beforeSend | Prima dell’invio dell’email | Messaggio modificato, false, o void | No |
email:deliver | Consegna email tramite transport | void | Sì |
email:afterSend | Dopo l’invio dell’email | void | No |
comment:beforeCreate | Prima del salvataggio del commento | Evento modificato, false, o void | No |
comment:moderate | Decidere lo stato del commento | { status, reason? } | Sì |
comment:afterCreate | Dopo il salvataggio del commento | void | No |
comment:afterModerate | L’admin modifica lo stato del commento | void | No |
page:metadata | Rendering della pagina | Contributi o null | No |
page:fragments | Rendering della pagina (trusted) | Contributi o null | No |
Consulta il riferimento agli hook per tipi di evento completi e firme dei gestori.