Gli hooks permettono ai plugin di eseguire codice in risposta agli eventi. Tutti gli hooks ricevono un oggetto evento e il contesto del plugin, e vengono dichiarati al momento della definizione del plugin — non c’è registrazione dinamica a runtime.
Questa pagina copre i plugin sandbox. Gli hooks funzionano in modo identico nei plugin nativi; i plugin nativi possono inoltre registrare page:fragments.
Firma dell’hook
Ogni gestore di hook prende due argomenti:
async (event, ctx) => ReturnType;
event— dati su ciò che è appena successo (contenuto in fase di salvataggio, media caricati, transizione del ciclo di vita, ecc.)ctx— ilPluginContextcon storage, KV, logging e API controllate da capacità
satisfies SandboxedPlugin sull’export predefinito inferisce event dal nome dell’hook (il tipo di evento canonico completo) e ctx come PluginContext, quindi i gestori non necessitano di annotazioni dei parametri. Per riferire un tipo di evento per nome in un helper, importalo da emdash/plugin.
Configurazione degli hooks
Un hook può essere dichiarato come gestore semplice o avvolto in un oggetto di configurazione:
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 dei plugin che devono essere eseguiti prima di questo hook. |
errorPolicy | "abort" | "continue" | "abort" | Se fermare o meno la pipeline in caso di errore. |
exclusive | boolean | false | Solo un plugin può essere il provider attivo. Usato per email:deliver e comment:moderate. |
handler | function | — | La funzione gestore dell’hook. Obbligatoria. |
Hooks del ciclo di vita
Vengono eseguiti durante l’installazione, attivazione, disattivazione e rimozione 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...");
await ctx.kv.set("settings:enabled", true);
await ctx.storage.items.put("default", { name: "Default Item" });
},
Evento: {} — Ritorna: Promise<void>
plugin:activate
Viene eseguito quando il plugin viene abilitato (dopo l’installazione o quando viene riabilitato).
"plugin:activate": async (_event, ctx) => {
ctx.log.info("Plugin activated");
},
Evento: {} — Ritorna: Promise<void>
plugin:deactivate
Viene eseguito quando il plugin viene disabilitato (ma non rimosso).
"plugin:deactivate": async (_event, ctx) => {
ctx.log.info("Plugin deactivated");
},
Evento: {} — Ritorna: 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) {
const result = await ctx.storage.items.query({ limit: 1000 });
await ctx.storage.items.deleteMany(result.items.map((i) => i.id));
}
},
Evento: { deleteData: boolean } — Ritorna: Promise<void>
Hooks di contenuto
Vengono eseguiti durante le operazioni di creazione, aggiornamento ed eliminazione sui contenuti del sito.
content:beforeSave
Viene eseguito prima che il contenuto venga salvato. Ritorna contenuto modificato o void per lasciarlo invariato. Lancia un’eccezione per annullare.
"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;
},
Evento: { content, collection, isNew } — Ritorna: contenuto modificato o void.
content:afterSave
Viene eseguito dopo che il contenuto è stato salvato con successo. Usalo per effetti collaterali come notifiche, logging o sincronizzazioni esterne.
"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 }),
});
}
},
Evento: { content, collection, isNew } — Ritorna: Promise<void>
content:beforeDelete
Viene eseguito prima che il contenuto venga eliminato. Ritorna false per annullare; true o void lo permette.
"content:beforeDelete": async (event, ctx) => {
if (event.collection === "pages" && event.id === "home") {
ctx.log.warn("Cannot delete home page");
return false;
}
return true;
},
Evento: { id, collection } — Ritorna: boolean | void
content:afterDelete
Viene eseguito dopo che il contenuto è stato eliminato con successo.
"content:afterDelete": async (event, ctx) => {
await ctx.storage.cache.delete(`${event.collection}:${event.id}`);
},
Evento: { id, collection } — Ritorna: Promise<void>
content:afterPublish
Viene eseguito dopo che il contenuto viene promosso da bozza a pubblicato. Richiede la capacità content:read.
Evento: { content, collection } — Ritorna: Promise<void>
content:afterUnpublish
Viene eseguito dopo che il contenuto viene riportato da pubblicato a bozza. Richiede la capacità content:read.
Evento: { content, collection } — Ritorna: Promise<void>
Hooks dei media
media:beforeUpload
Viene eseguito prima che un file venga caricato. Ritorna metadati del file modificati o lancia un’eccezione per annullare.
"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}` };
},
Evento: { file: { name, type, size } } — Ritorna: file modificato o void
media:afterUpload
Viene eseguito dopo che un file è stato caricato con successo.
Evento: { media: { id, filename, mimeType, size, url, createdAt } } — Ritorna: Promise<void>
Hooks delle pagine pubbliche
Questi permettono ai plugin di contribuire alle pagine pubbliche renderizzate. I template optano includendo i componenti <EmDashHead>, <EmDashBodyStart> e <EmDashBodyEnd> da emdash/ui.
page:metadata
Contribuisce metadati tipizzati a <head> — meta tag, proprietà OpenGraph, rel <link> consentiti e JSON-LD. Disponibile sia per plugin sandbox che nativi. Core valida, deduplica e renderizza i contributi; i plugin ritornano 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 };
}
}
Ritorna: PageMetadataContribution | PageMetadataContribution[] | null
Tipi di contributo:
| Tipo | Renderizza | Chiave di deduplicazione |
|---|---|---|
meta | <meta name="..." content="..."> | key o name |
property | <meta property="..." content="..."> | key o property |
link | <link rel="canonical|alternate" href="..."> | canonical: singleton; alternate: key o hreflang |
jsonld | <script type="application/ld+json"> | id (se presente) |
Il primo contributo vince per qualsiasi chiave di deduplicazione. Il rel di link è limitato a una whitelist bloccata per sicurezza (canonical, alternate, author, license, nlweb, site.standard.document); href deve essere HTTP o HTTPS.
page:fragments
Contribuisce HTML grezzo, script o fogli di stile ai punti di inserimento della pagina. Solo plugin nativi.
I plugin sandbox non possono usare questo hook perché il suo output viene eseguito come codice di prima parte nel browser del visitatore, al di fuori di qualsiasi confine sandbox. Per contributi di pagina sicuri per sandbox, usa page:metadata. Vedi Plugin nativi: frammenti di pagina se hai bisogno di questa superficie.
Ordine di esecuzione degli hooks
Gli hooks vengono eseguiti in questo ordine:
- Gli hooks con valori di
prioritypiù bassi vengono eseguiti per primi. - Per priorità uguali, gli hooks vengono eseguiti nell’ordine di registrazione del plugin.
- Gli hooks con
dependenciesaspettano che quei plugin vengano completati.
// 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 () => {},
}
Gestione degli errori
Quando un hook lancia un’eccezione o va in timeout:
errorPolicy: "abort"— l’intera pipeline si ferma e l’operazione originale potrebbe fallire.errorPolicy: "continue"— l’errore viene registrato e gli hooks rimanenti continuano a essere eseguiti.
"content:afterSave": {
timeout: 5000,
errorPolicy: "continue",
handler: async (event, ctx) => {
await ctx.http!.fetch("https://unreliable-api.com/notify");
},
},
Timeout
Gli hooks hanno un timeout predefinito di 5000ms. Aumenta il timeout per lavori più lenti:
"content:afterSave": {
timeout: 30000,
handler: async (event, ctx) => {
// Long-running operation
},
},
Riferimento degli hooks
| Hook | Trigger | Ritorna | 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 permetti | No |
content:afterDelete | Dopo l’eliminazione del contenuto | void | No |
content:afterPublish | Dopo la pubblicazione del contenuto | void | No |
content:afterUnpublish | Dopo la rimozione dalla pubblicazione | 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 | Attivazione attività programmata | void | No |
email:beforeSend | Prima dell’invio email | Messaggio modificato, false o void | No |
email:deliver | Consegna email via transport | void | Sì |
email:afterSend | Dopo l’invio 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 | Admin cambia stato del commento | void | No |
page:metadata | Rendering della pagina | Contributi o null | No |
page:fragments | Rendering della pagina (solo nativo) | Contributi o null | No |
Vedi il Riferimento degli Hooks per tipi di eventi completi e firme dei gestori.