Ir al contenido

Ganchos de Plugin

Los hooks permiten que los plugins ejecuten código en respuesta a eventos. Todos los hooks reciben un objeto de evento y el contexto del plugin. Los hooks se declaran en el momento de la definición del plugin, no se registran dinámicamente en tiempo de ejecución.

Cada manejador de hook recibe dos argumentos:

async (event: EventType, ctx: PluginContext) => ReturnType;
  • event — Datos sobre el evento (contenido que se está guardando, medios subidos, etc.)
  • ctx — El contexto del plugin con almacenamiento, KV, registro y APIs controladas por capacidades

Los hooks se pueden declarar como un manejador simple o con configuración completa:

hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
}
}
OpciónTipoPor defectoDescripción
prioritynumber100Orden de ejecución. Los números más bajos se ejecutan primero.
timeoutnumber5000Tiempo máximo de ejecución en milisegundos.
dependenciesstring[][]IDs de plugins que deben ejecutarse antes de este hook.
errorPolicy"abort" | "continue""abort"Si se debe detener la canalización en caso de error.
exclusivebooleanfalseSolo un plugin puede ser el proveedor activo. Se usa para email:deliver y comment:moderate.
handlerfunction—La función manejadora del hook. Requerida.

Los hooks del ciclo de vida se ejecutan durante la instalación, activación y desactivación del plugin.

Se ejecuta una vez cuando el plugin se añade por primera vez a un sitio.

"plugin:install": async (_event, ctx) => {
ctx.log.info("Installing plugin...");
// Inicializar datos por defecto
await ctx.kv.set("settings:enabled", true);
await ctx.storage.items!.put("default", { name: "Default Item" });
}

Evento: {}
Retorna: Promise<void>

Se ejecuta cuando el plugin se habilita (después de la instalación o cuando se vuelve a habilitar).

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

Evento: {}
Retorna: Promise<void>

Se ejecuta cuando el plugin se deshabilita (pero no se elimina).

"plugin:deactivate": async (_event, ctx) => {
ctx.log.info("Plugin deactivated");
// Release resources, pause background work
}

Evento: {}
Retorna: Promise<void>

Se ejecuta cuando el plugin se elimina de un sitio.

"plugin:uninstall": async (event, ctx) => {
ctx.log.info("Uninstalling plugin...");
if (event.deleteData) {
// El usuario optó por eliminar los datos del plugin
const result = await ctx.storage.items!.query({ limit: 1000 });
await ctx.storage.items!.deleteMany(result.items.map(i => i.id));
}
}

Evento: { deleteData: boolean }
Retorna: Promise<void>

Los hooks de contenido se ejecutan durante las operaciones de creación, actualización y eliminación.

Se ejecuta antes de que se guarde el contenido. Retorna contenido modificado o void para mantenerlo sin cambios. Lanza una excepción para cancelar el guardado.

"content:beforeSave": async (event, ctx) => {
const { content, collection, isNew } = event;
// Validar
if (collection === "posts" && !content.title) {
throw new Error("Posts require a title");
}
// Transformar
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
}

Retorna: Promise<Record<string, unknown> | void>

Se ejecuta después de que el contenido se guarda exitosamente. Úsalo para efectos secundarios como notificaciones, registro o sincronización con sistemas externos.

"content:afterSave": async (event, ctx) => {
const { content, collection, isNew } = event;
ctx.log.info(`${isNew ? "Created" : "Updated"} ${collection}/${content.id}`);
// Disparar sincronización externa
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;
}

Retorna: Promise<void>

Se ejecuta antes de que se elimine el contenido. Retorna false para cancelar la eliminación, true o void para permitirla.

"content:beforeDelete": async (event, ctx) => {
const { id, collection } = event;
// Prevenir eliminación de contenido protegido
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;
}

Retorna: Promise<boolean | void>

Se ejecuta después de que el contenido se elimina exitosamente.

"content:afterDelete": async (event, ctx) => {
const { id, collection } = event;
ctx.log.info(`Eliminado ${collection}/${id}`);
// Limpiar datos relacionados del plugin
await ctx.storage.cache!.delete(`${collection}:${id}`);
}

Evento:

{
id: string;
collection: string;
}

Retorna: Promise<void>

Los hooks de medios se ejecutan durante las subidas de archivos.

Se ejecuta antes de que se suba un archivo. Retorna información del archivo modificada o void para mantenerla sin cambios. Lanza una excepción para cancelar la subida.

"media:beforeUpload": async (event, ctx) => {
const { file } = event;
// Validar tipo de archivo
if (!file.type.startsWith("image/")) {
throw new Error("Only images are allowed");
}
// Validar tamaño del archivo (máximo 10MB)
if (file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
// Renombrar archivo
return {
...file,
name: `${Date.now()}-${file.name}`
};
}

Evento:

{
file: {
name: string; // Original filename
type: string; // MIME type
size: number; // Size in bytes
}
}

Retorna: Promise<{ name: string; type: string; size: number } | void>

Se ejecuta después de que un archivo se sube exitosamente.

"media:afterUpload": async (event, ctx) => {
const { media } = event;
ctx.log.info(`Subido ${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;
}
}

Retorna: Promise<void>

Los hooks se ejecutan en este orden:

  1. Los hooks con valores de priority más bajos se ejecutan primero
  2. Para prioridades iguales, los hooks se ejecutan en el orden de registro del plugin
  3. Los hooks con dependencies esperan a que esos plugins terminen
// Plugin A
"content:afterSave": {
priority: 50, // Runs first
handler: async () => {}
}
// Plugin B
"content:afterSave": {
priority: 100, // Se ejecuta segundo (prioridad por defecto)
handler: async () => {}
}
// Plugin C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // Se ejecuta después de A, incluso si la prioridad era menor
handler: async () => {}
}

Cuando un hook lanza una excepción o se agota el tiempo:

  • errorPolicy: "abort" — Toda la canalización se detiene. La operación original puede fallar.
  • `errorPolicy: “continue” — El error se registra, y los hooks restantes aún se ejecutan.
"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");
}
}

Los hooks tienen un tiempo de espera por defecto de 5000ms (5 segundos). Auméntalo para operaciones que puedan tardar más:

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

Los hooks de página pública permiten a los plugins contribuir al <head> y <body> de las páginas renderizadas. Las plantillas se suscriben usando los componentes <EmDashHead>, <EmDashBodyStart>, y <EmDashBodyEnd> de emdash/ui.

Contribuye con metadatos tipados al <head> — etiquetas meta, propiedades OpenGraph, enlaces canónicos/alternativos y datos estructurados JSON-LD. Funciona tanto en modo confiable como en sandbox.

El núcleo valida, elimina duplicados y renderiza las contribuciones. Los plugins devuelven datos estructurados, nunca HTML crudo.

"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.title,
description: event.page.description,
},
};
}

Evento:

{
page: {
url: string;
path: string;
locale: string | null;
kind: "content" | "custom";
pageType: string;
title: string | null;
description: string | null;
canonical: string | null;
image: string | null;
content?: { collection: string; id: string; slug: string | null };
}
}

Devuelve: PageMetadataContribution | PageMetadataContribution[] | null

Tipos de contribución:

TipoRenderizaClave de deduplicación
meta<meta name="..." content="...">key o name
property<meta property="..." content="...">key o property
link<link rel="canonical|alternate" href="...">canónico: único; alternativo: key o hreflang
jsonld<script type="application/ld+json">id (si está presente)

La primera contribución gana para cualquier clave de deduplicación. Los href de los enlaces deben ser HTTP o HTTPS.

Contribuye con HTML crudo, scripts o marcado a los puntos de inserción de la página. Solo plugins confiables — los plugins en sandbox no pueden usar este hook.

"page:fragments": async (event, ctx) => {
return {
kind: "external-script",
placement: "head",
src: "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX",
async: true,
};
}

Devuelve: PageFragmentContribution | PageFragmentContribution[] | null

Ubicaciones: "head", "body:start", "body:end". Las plantillas que omiten un componente para una ubicación ignoran silenciosamente las contribuciones dirigidas a ella.

HookDisparadorRetornoExclusivo
plugin:installPrimera instalación del pluginvoidNo
plugin:activatePlugin habilitadovoidNo
plugin:deactivatePlugin deshabilitadovoidNo
plugin:uninstallPlugin eliminadovoidNo
content:beforeSaveAntes de guardar contenidoContenido modificado o voidNo
content:afterSaveDespués de guardar contenidovoidNo
content:beforeDeleteAntes de eliminar contenidofalse para cancelar, de lo contrario permitirNo
content:afterDeleteDespués de eliminar contenidovoidNo
media:beforeUploadAntes de subir archivoInformación del archivo modificada o voidNo
media:afterUploadDespués de subir archivovoidNo
cronSe activa tarea programadavoidNo
email:beforeSendAntes de enviar correoMensaje modificado, false, o voidNo
email:deliverEntregar correo vía transportevoidSí
email:afterSendDespués de enviar correovoidNo
comment:beforeCreateAntes de almacenar comentarioEvento modificado, false, o voidNo
comment:moderateDecidir estado del comentario{ status, reason? }Sí
comment:afterCreateDespués de almacenar comentariovoidNo
comment:afterModerateAdmin cambia estado del comentariovoidNo
page:metadataRenderizado de páginaContribuciones o nullNo
page:fragmentsRenderizado de página (confiable)Contribuciones o nullNo

Consulta la Referencia de Hooks para ver los tipos de evento completos y las firmas de los manejadores.