Pular para o conteúdo

Ganchos de Plugin

Os hooks permitem que os plugins executem código em resposta a eventos. Todos os hooks recebem um objeto de evento e o contexto do plugin. Os hooks são declarados no momento da definição do plugin, não registrados dinamicamente em tempo de execução.

Cada manipulador de hook recebe dois argumentos:

async (event: EventType, ctx: PluginContext) => ReturnType;
  • event — Dados sobre o evento (conteúdo sendo salvo, mídia carregada, etc.)
  • ctx — O contexto do plugin com armazenamento, KV, registro de logs e APIs controladas por capacidade

Os hooks podem ser declarados como um manipulador simples ou com configuração completa:

hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
}
}
OpçãoTipoPadrãoDescrição
prioritynumber100Ordem de execução. Números menores executam primeiro.
timeoutnumber5000Tempo máximo de execução em milissegundos.
dependenciesstring[][]IDs de plugins que devem executar antes deste hook.
errorPolicy"abort" | "continue""abort"Se deve parar o pipeline em caso de erro.
exclusivebooleanfalseApenas um plugin pode ser o provedor ativo. Usado para email:deliver e comment:moderate.
handlerfunction—A função manipuladora do hook. Obrigatória.

Os hooks de ciclo de vida são executados durante a instalação, ativação e desativação do plugin.

Executa uma vez quando o plugin é adicionado pela primeira vez a um site.

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

Evento: {}
Retorna: Promise<void>

Executa quando o plugin é ativado (após a instalação ou quando reativado).

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

Evento: {}
Retorna: Promise<void>

Executa quando o plugin é desativado (mas não removido).

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

Evento: {}
Retorna: Promise<void>

Executa quando o plugin é removido de um site.

"plugin:uninstall": async (event, ctx) => {
ctx.log.info("Uninstalling plugin...");
if (event.deleteData) {
// Usuário optou por excluir dados do 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>

Os hooks de conteúdo são executados durante operações de criação, atualização e exclusão.

Executa antes do conteúdo ser salvo. Retorne conteúdo modificado ou void para mantê-lo inalterado. Lance uma exceção para cancelar o salvamento.

"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>

Executa após o conteúdo ser salvo com sucesso. Use para efeitos colaterais como notificações, registro de logs ou sincronização com sistemas externos.

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

Executa antes do conteúdo ser excluído. Retorne false para cancelar a exclusão, true ou void para permitir.

"content:beforeDelete": async (event, ctx) => {
const { id, collection } = event;
// Impedir exclusão de conteúdo 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>

Executa após o conteúdo ser excluído com sucesso.

"content:afterDelete": async (event, ctx) => {
const { id, collection } = event;
ctx.log.info(`Excluído ${collection}/${id}`);
// Limpar dados relacionados do plugin
await ctx.storage.cache!.delete(`${collection}:${id}`);
}

Evento:

{
id: string;
collection: string;
}

Retorna: Promise<void>

Os hooks de mídia são executados durante uploads de arquivos.

Executa antes de um arquivo ser carregado. Retorne informações do arquivo modificadas ou void para mantê-las inalteradas. Lance uma exceção para cancelar o upload.

"media:beforeUpload": async (event, ctx) => {
const { file } = event;
// Validar tipo de arquivo
if (!file.type.startsWith("image/")) {
throw new Error("Only images are allowed");
}
// Validar tamanho do arquivo (máx. 10MB)
if (file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
// Renomear arquivo
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>

Executa após um arquivo ser carregado com sucesso.

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

Os hooks são executados nesta ordem:

  1. Hooks com valores de priority mais baixos executam primeiro
  2. Para prioridades iguais, os hooks executam na ordem de registro do plugin
  3. Hooks com dependencies aguardam a conclusão desses plugins
// Plugin A
"content:afterSave": {
priority: 50, // Runs first
handler: async () => {}
}
// Plugin B
"content:afterSave": {
priority: 100, // Executa segundo (prioridade padrão)
handler: async () => {}
}
// Plugin C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // Executa após A, mesmo se a prioridade fosse menor
handler: async () => {}
}

Quando um hook lança uma exceção ou atinge o tempo limite:

  • errorPolicy: "abort" — O pipeline inteiro para. A operação original pode falhar.
  • errorPolicy: "continue" — O erro é registrado e os hooks restantes ainda são executados.
"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");
}
}

Os hooks têm um tempo limite padrão de 5000ms (5 segundos). Aumente-o para operações que podem levar mais tempo:

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

Hooks de página pública permitem que plugins contribuam para o <head> e <body> das páginas renderizadas. Os templates optam por usar os componentes <EmDashHead>, <EmDashBodyStart> e <EmDashBodyEnd> do emdash/ui.

Contribui com metadados tipados para <head> — tags meta, propriedades OpenGraph, links canônicos/alternativos e dados estruturados JSON-LD. Funciona tanto no modo confiável quanto no sandbox.

O Core valida, deduplica e renderiza as contribuições. Os plugins retornam dados estruturados, nunca HTML bruto.

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

Retorna: PageMetadataContribution | PageMetadataContribution[] | null

Tipos de contribuição:

TipoRenderizaChave de deduplicação
meta<meta name="..." content="...">key ou name
property<meta property="..." content="...">key ou property
link<link rel="canonical|alternate" href="...">canônico: singleton; alternativo: key ou hreflang
jsonld<script type="application/ld+json">id (se presente)

A primeira contribuição vence para qualquer chave de deduplicação. Os hrefs dos links devem ser HTTP ou HTTPS.

Contribui com HTML bruto, scripts ou marcação para pontos de inserção na página. Apenas plugins confiáveis — plugins em sandbox não podem 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,
};
}

Retorna: PageFragmentContribution | PageFragmentContribution[] | null

Posicionamentos: "head", "body:start", "body:end". Templates que omitem um componente para um posicionamento ignoram silenciosamente as contribuições direcionadas a ele.

HookGatilhoRetornoExclusivo
plugin:installPrimeira instalação do pluginvoidNão
plugin:activatePlugin ativadovoidNão
plugin:deactivatePlugin desativadovoidNão
plugin:uninstallPlugin removidovoidNão
content:beforeSaveAntes de salvar conteúdoConteúdo modificado ou voidNão
content:afterSaveApós salvar conteúdovoidNão
content:beforeDeleteAntes de deletar conteúdofalse para cancelar, senão permitirNão
content:afterDeleteApós deletar conteúdovoidNão
media:beforeUploadAntes de upload de arquivoInformações do arquivo modificadas ou voidNão
media:afterUploadApós upload de arquivovoidNão
cronTarefa agendada disparavoidNão
email:beforeSendAntes do envio de emailMensagem modificada, false, ou voidNão
email:deliverEntrega email via transportevoidSim
email:afterSendApós envio de emailvoidNão
comment:beforeCreateAntes de comentário armazenadoEvento modificado, false, ou voidNão
comment:moderateDecide status do comentário{ status, reason? }Sim
comment:afterCreateApós comentário armazenadovoidNão
comment:afterModerateAdmin altera status do comentáriovoidNão
page:metadataRenderização de páginaContribuições ou nullNão
page:fragmentsRenderização de página (confiável)Contribuições ou nullNão

Consulte a Referência de Hooks para tipos de evento completos e assinaturas de manipuladores.