Hooks

In questa pagina

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 — il PluginContext con 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

OpzioneTipoPredefinitoDescrizione
prioritynumber100Ordine di esecuzione. I numeri più bassi vengono eseguiti per primi.
timeoutnumber5000Tempo massimo di esecuzione in millisecondi.
dependenciesstring[][]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.
exclusivebooleanfalseSolo un plugin può essere il provider attivo. Usato per email:deliver e comment:moderate.
handlerfunctionLa 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:

TipoRenderizzaChiave 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:

  1. Gli hooks con valori di priority più bassi vengono eseguiti per primi.
  2. Per priorità uguali, gli hooks vengono eseguiti nell’ordine di registrazione del plugin.
  3. Gli hooks con dependencies aspettano 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

HookTriggerRitornaEsclusivo
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 permettiNo
content:afterDeleteDopo l’eliminazione del contenutovoidNo
content:afterPublishDopo la pubblicazione del contenutovoidNo
content:afterUnpublishDopo la rimozione dalla pubblicazionevoidNo
media:beforeUploadPrima del caricamento del fileInfo file modificate o voidNo
media:afterUploadDopo il caricamento del filevoidNo
cronAttivazione attività programmatavoidNo
email:beforeSendPrima dell’invio emailMessaggio modificato, false o voidNo
email:deliverConsegna email via transportvoid
email:afterSendDopo l’invio 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:afterModerateAdmin cambia stato del commentovoidNo
page:metadataRendering della paginaContributi o nullNo
page:fragmentsRendering della pagina (solo nativo)Contributi o nullNo

Vedi il Riferimento degli Hooks per tipi di eventi completi e firme dei gestori.