Hooks

In questa pagina

Gli hooks consentono 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 tratta i plugin sandboxed (formato standard). Gli hooks funzionano in modo identico nei plugin nativi; l’unica differenza è che i plugin nativi possono anche registrare page:fragments, cosa che i plugin sandboxed non possono fare.

Firma dell’Hook

Ogni gestore di hook prende due argomenti:

async (event: EventType, ctx: PluginContext) => ReturnType;
  • event — dati su ciò che è appena successo (contenuto salvato, media caricati, transizione del ciclo di vita, ecc.)
  • ctx — il PluginContext con storage, KV, logging e API controllate dalle capability

Configurazione degli Hook

Un hook può essere dichiarato come un semplice gestore o avvolto in un oggetto di configurazione:

Simple

hooks: {
	"content:afterSave": async (event, ctx) => {
		ctx.log.info("Content saved");
	},
},

Full config

hooks: {
	"content:afterSave": {
		priority: 100,
		timeout: 5000,
		dependencies: ["audit-log"],
		errorPolicy: "continue",
		handler: async (event, ctx) => {
			ctx.log.info("Content saved");
		},
	},
},

Opzioni di configurazione

OpzioneTipoDefaultDescrizione
prioritynumber100Ordine di esecuzione. I numeri inferiori 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 interrompere 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. Richiesto.

Hooks del ciclo di vita

Vengono eseguiti durante l’installazione, l’attivazione, la disattivazione e la 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" });
},

Event: {}Returns: 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");
},

Event: {}Returns: Promise<void>

plugin:deactivate

Viene eseguito quando il plugin viene disabilitato (ma non rimosso).

"plugin:deactivate": async (_event, ctx) => {
	ctx.log.info("Plugin deactivated");
},

Event: {}Returns: 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));
	}
},

Event: { deleteData: boolean }Returns: Promise<void>

Hooks dei contenuti

Vengono eseguiti durante le operazioni di creazione, aggiornamento ed eliminazione dei contenuti del sito.

content:beforeSave

Viene eseguito prima che il contenuto venga salvato. Restituisci contenuto modificato o void per lasciarlo invariato. Lancia un errore 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;
},

Event: { content, collection, isNew }Returns: 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 }),
		});
	}
},

Event: { content, collection, isNew }Returns: Promise<void>

content:beforeDelete

Viene eseguito prima che il contenuto venga eliminato. Restituisci false per annullare; true o void lo consente.

"content:beforeDelete": async (event, ctx) => {
	if (event.collection === "pages" && event.id === "home") {
		ctx.log.warn("Cannot delete home page");
		return false;
	}
	return true;
},

Event: { id, collection }Returns: 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}`);
},

Event: { id, collection }Returns: Promise<void>

content:afterPublish

Viene eseguito dopo che il contenuto è stato promosso da bozza a live. Richiede la capability content:read.

Event: { content, collection }Returns: Promise<void>

content:afterUnpublish

Viene eseguito dopo che il contenuto è stato ripristinato da live a bozza. Richiede la capability content:read.

Event: { content, collection }Returns: Promise<void>

Hooks dei media

media:beforeUpload

Viene eseguito prima che un file venga caricato. Restituisci metadati del file modificati o lancia un errore 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}` };
},

Event: { file: { name, type, size } }Returns: file modificato o void

media:afterUpload

Viene eseguito dopo che un file è stato caricato con successo.

Event: { media: { id, filename, mimeType, size, url, createdAt } }Returns: Promise<void>

Hooks delle pagine pubbliche

Questi consentono ai plugin di contribuire alle pagine pubbliche renderizzate. I template devono aderire includendo i componenti <EmDashHead>, <EmDashBodyStart> e <EmDashBodyEnd> da emdash/ui.

page:metadata

Contribuisce metadati tipizzati a <head> — meta tag, proprietà OpenGraph, <link> rels consentiti e JSON-LD. Disponibile sia per plugin sandboxed che nativi. Il core valida, 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,
		},
	};
},

Event:

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

Returns: PageMetadataContribution | PageMetadataContribution[] | null

Tipi di contributo:

KindRenderizzaChiave di deduplica
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 deduplica. Link rel è 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 sandboxed non possono usare questo hook perché il suo output viene eseguito come codice first-party 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 hook

Gli hook vengono eseguiti in questo ordine:

  1. Gli hook con valori di priority inferiori vengono eseguiti per primi.
  2. Per priorità uguali, gli hook vengono eseguiti nell’ordine di registrazione del plugin.
  3. Gli hook con dependencies attendono il completamento di quei plugin.
// 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 errore o supera il timeout:

  • errorPolicy: "abort" — l’intera pipeline si ferma e l’operazione originaria può fallire.
  • errorPolicy: "continue" — l’errore viene registrato e gli hook rimanenti vengono comunque eseguiti.
"content:afterSave": {
	timeout: 5000,
	errorPolicy: "continue",
	handler: async (event, ctx) => {
		await ctx.http!.fetch("https://unreliable-api.com/notify");
	},
},

Timeout

Gli hook hanno un default di 5000ms. Aumenta il timeout per lavori più lenti:

"content:afterSave": {
	timeout: 30000,
	handler: async (event, ctx) => {
		// Long-running operation
	},
},

Riferimento degli hook

HookTriggerReturnEsclusivo
plugin:installPrima installazione pluginvoidNo
plugin:activatePlugin abilitatovoidNo
plugin:deactivatePlugin disabilitatovoidNo
plugin:uninstallPlugin rimossovoidNo
content:beforeSavePrima del salvataggio contenutoContenuto modificato o voidNo
content:afterSaveDopo il salvataggio contenutovoidNo
content:beforeDeletePrima dell’eliminazione contenutofalse per annullare, altrimenti consentiNo
content:afterDeleteDopo l’eliminazione contenutovoidNo
content:afterPublishDopo la pubblicazione contenutovoidNo
content:afterUnpublishDopo la depubblicazione contenutovoidNo
media:beforeUploadPrima del caricamento fileInfo file modificata o voidNo
media:afterUploadDopo il caricamento filevoidNo
cronTask pianificato si attivavoidNo
email:beforeSendPrima dell’invio emailMessaggio modificato, false o voidNo
email:deliverConsegna email via transportvoid
email:afterSendDopo l’invio emailvoidNo
comment:beforeCreatePrima della memorizzazione commentoEvento modificato, false o voidNo
comment:moderateDecidere stato commento{ status, reason? }
comment:afterCreateDopo la memorizzazione commentovoidNo
comment:afterModerateAdmin cambia stato commentovoidNo
page:metadataRendering paginaContributi o nullNo
page:fragmentsRendering pagina (solo nativo)Contributi o nullNo

Vedi il Riferimento Hook per i tipi di eventi completi e le firme dei gestori.