Hook dei plugin

In questa pagina

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

OpzioneTipoPredefinitoDescrizione
prioritynumber100Ordine di esecuzione. I numeri più bassi vengono eseguiti per primi.
timeoutnumber5000Tempo massimo di esecuzione in millisecondi.
dependenciesstring[][]ID di plugin che devono essere eseguiti prima di questo hook.
errorPolicy"abort" | "continue""abort"Se interrompere la pipeline in caso di errore.
exclusivebooleanfalseUn solo plugin può essere il provider attivo. Usato per email:deliver e comment:moderate.
handlerfunctionFunzione 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:

  1. Gli hook con valori priority più bassi vengono eseguiti per primi
  2. A parità di priorità, gli hook vengono eseguiti nell’ordine di registrazione dei plugin
  3. Gli hook con dependencies attendono 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:

KindOutputChiave 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

HookAttivazioneValore restituitoEsclusivo
plugin:installPrima installazione del pluginvoidNo
plugin:activatePlugin abilitatovoidNo
plugin:deactivatePlugin disabilitatovoidNo
plugin:uninstallPlugin rimossovoidNo
content:beforeSavePrima del salvataggio del contenutoContenuto modificato o voidNo
content:afterSaveDopo il salvataggio del contenutovoidNo
content:beforeDeletePrima dell’eliminazione del contenutofalse per annullare, altrimenti consentiNo
content:afterDeleteDopo l’eliminazione del contenutovoidNo
content:afterPublishDopo la pubblicazione del contenutovoidNo
content:afterUnpublishDopo la depubblicazione del contenutovoidNo
media:beforeUploadPrima del caricamento del fileInfo file modificate o voidNo
media:afterUploadDopo il caricamento del filevoidNo
cronEsecuzione di un’attività pianificatavoidNo
email:beforeSendPrima dell’invio dell’emailMessaggio modificato, false, o voidNo
email:deliverConsegna email tramite transportvoid
email:afterSendDopo l’invio dell’emailvoidNo
comment:beforeCreatePrima del salvataggio del commentoEvento modificato, false, o voidNo
comment:moderateDecidere lo stato del commento{ status, reason? }
comment:afterCreateDopo il salvataggio del commentovoidNo
comment:afterModerateL’admin modifica lo stato del commentovoidNo
page:metadataRendering della paginaContributi o nullNo
page:fragmentsRendering della pagina (trusted)Contributi o nullNo

Consulta il riferimento agli hook per tipi di evento completi e firme dei gestori.