コンテンツにスキップ

プラグインフック

フックにより、プラグインはイベントに応じてコードを実行できます。すべてのフックはイベントオブジェクトとプラグインコンテキストを受け取ります。フックはプラグイン定義時に宣言され、実行時に動的に登録されるものではありません。

すべてのフックハンドラーは2つの引数を受け取ります:

async (event: EventType, ctx: PluginContext) => ReturnType;
  • event — イベントに関するデータ(保存されるコンテンツ、アップロードされたメディアなど)
  • ctx — ストレージ、KV、ロギング、および権限制限付きAPIを含むプラグインコンテキスト

フックは、シンプルなハンドラーとして、または完全な設定とともに宣言できます:

hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
}
}
オプション型デフォルト説明
prioritynumber100実行順序。数値が小さいほど先に実行されます。
timeoutnumber5000最大実行時間(ミリ秒)。
dependenciesstring[][]このフックの前に実行する必要があるプラグインID。
errorPolicy"abort" | "continue""abort"エラー時にパイプラインを停止するかどうか。
exclusivebooleanfalse1つのプラグインのみがアクティブなプロバイダーになれます。email:deliverおよびcomment:moderateで使用されます。
handlerfunction—フックハンドラー関数。必須です。

ライフサイクルフックは、プラグインのインストール、有効化、および無効化中に実行されます。

プラグインがサイトに初めて追加されたときに1回実行されます。

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

イベント: {}
戻り値: Promise<void>

プラグインが有効化されたとき(インストール後または再有効化時)に実行されます。

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

イベント: {}
戻り値: Promise<void>

プラグインが無効化されたとき(削除されていない場合)に実行されます。

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

イベント: {}
戻り値: Promise<void>

プラグインがサイトから削除されたときに実行されます。

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

イベント: { deleteData: boolean }
戻り値: Promise<void>

コンテンツフックは、作成、更新、および削除操作中に実行されます。

コンテンツが保存される前に実行されます。変更されたコンテンツを返すか、voidを返して変更しないままにします。スローすると保存がキャンセルされます。

"content:beforeSave": async (event, ctx) => {
const { content, collection, isNew } = event;
// 検証
if (collection === "posts" && !content.title) {
throw new Error("Posts require a title");
}
// 変換
if (content.slug) {
content.slug = content.slug.toLowerCase().replace(/\s+/g, "-");
}
return content;
}

イベント:

{
content: Record<string, unknown>; // Content data being saved
collection: string; // Collection name
isNew: boolean; // True if creating, false if updating
}

戻り値: Promise<Record<string, unknown> | void>

コンテンツが正常に保存された後に実行されます。通知、ロギング、外部システムへの同期などの副作用に使用します。

"content:afterSave": async (event, ctx) => {
const { content, collection, isNew } = event;
ctx.log.info(`${isNew ? "Created" : "Updated"} ${collection}/${content.id}`);
// 外部同期をトリガー
if (ctx.http) {
await ctx.http.fetch("https://api.example.com/webhook", {
method: "POST",
body: JSON.stringify({ event: "content:save", id: content.id })
});
}
}

イベント:

{
content: Record<string, unknown>; // Saved content (includes id, timestamps)
collection: string;
isNew: boolean;
}

戻り値: Promise<void>

コンテンツが削除される前に実行されます。削除をキャンセルするにはfalseを返し、許可するにはtrueまたはvoidを返します。

"content:beforeDelete": async (event, ctx) => {
const { id, collection } = event;
// 保護されたコンテンツの削除を防止
if (collection === "pages" && id === "home") {
ctx.log.warn("Cannot delete home page");
return false;
}
return true;
}

イベント:

{
id: string; // Content ID being deleted
collection: string;
}

戻り値: Promise<boolean | void>

コンテンツが正常に削除された後に実行されます。

"content:afterDelete": async (event, ctx) => {
const { id, collection } = event;
ctx.log.info(`削除済み ${collection}/${id}`);
// 関連するプラグインデータをクリーンアップ
await ctx.storage.cache!.delete(`${collection}:${id}`);
}

イベント:

{
id: string;
collection: string;
}

戻り値: Promise<void>

メディアフックは、ファイルアップロード中に実行されます。

ファイルがアップロードされる前に実行されます。変更されたファイル情報を返すか、voidを返して変更しないままにします。スローするとアップロードがキャンセルされます。

"media:beforeUpload": async (event, ctx) => {
const { file } = event;
// ファイルタイプを検証
if (!file.type.startsWith("image/")) {
throw new Error("Only images are allowed");
}
// ファイルサイズを検証(最大10MB)
if (file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
// ファイル名を変更
return {
...file,
name: `${Date.now()}-${file.name}`
};
}

イベント:

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

戻り値: Promise<{ name: string; type: string; size: number } | void>

ファイルが正常にアップロードされた後に実行されます。

"media:afterUpload": async (event, ctx) => {
const { media } = event;
ctx.log.info(`アップロード済み ${media.filename}`, {
id: media.id,
size: media.size,
mimeType: media.mimeType
});
}

イベント:

{
media: {
id: string;
filename: string;
mimeType: string;
size: number | null;
url: string;
createdAt: string;
}
}

戻り値: Promise<void>

フックは次の順序で実行されます:

  1. priority値が小さいフックが最初に実行されます
  2. 同じ優先度の場合、プラグイン登録順に実行されます
  3. dependenciesを持つフックは、それらのプラグインが完了するまで待機します
// Plugin A
"content:afterSave": {
priority: 50, // Runs first
handler: async () => {}
}
// プラグイン B
"content:afterSave": {
priority: 100, // 2番目に実行(デフォルト優先度)
handler: async () => {}
}
// プラグイン C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // 優先度が低くてもAの後に実行
handler: async () => {}
}

フックがスローまたはタイムアウトした場合:

  • errorPolicy: "abort" — パイプライン全体が停止します。元の操作が失敗する可能性があります。
  • errorPolicy: "continue" — エラーがログに記録され、残りのフックは実行されます。
"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");
}
}

フックのデフォルトタイムアウトは5000ms(5秒)です。より時間がかかる可能性のある操作にはこれを延長してください:

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

パブリックページフックを使用すると、プラグインはレンダリングされたページの <head> と <body> に貢献できます。テンプレートは emdash/ui の <EmDashHead>、<EmDashBodyStart>、<EmDashBodyEnd> コンポーネントを使用してオプトインします。

型付きメタデータを <head> に貢献します — メタタグ、OpenGraphプロパティ、canonical/alternateリンク、JSON-LD構造化データ。信頼済みモードとサンドボックスモードの両方で動作します。

コアは貢献を検証、重複排除、レンダリングします。プラグインは生のHTMLではなく、構造化データを返します。

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

イベント:

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

戻り値: PageMetadataContribution | PageMetadataContribution[] | null

貢献タイプ:

種類レンダリング内容重複排除キー
meta<meta name="..." content="...">key または name
property<meta property="..." content="...">key または property
link<link rel="canonical|alternate" href="...">canonical: シングルトン; alternate: key または hreflang
jsonld<script type="application/ld+json">id (存在する場合)

任意の重複排除キーに対して、最初の貢献が優先されます。リンクのhrefはHTTPまたはHTTPSである必要があります。

生のHTML、スクリプト、またはマークアップをページの挿入ポイントに貢献します。信頼済みプラグインのみ — サンドボックス化されたプラグインはこのフックを使用できません。

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

戻り値: PageFragmentContribution | PageFragmentContribution[] | null

配置場所: "head"、"body:start"、"body:end"。配置場所に対応するコンポーネントを省略したテンプレートは、その配置場所をターゲットとする貢献を黙って無視します。

フックトリガー戻り値排他的
plugin:installプラグインの初回インストールvoidいいえ
plugin:activateプラグイン有効化voidいいえ
plugin:deactivateプラグイン無効化voidいいえ
plugin:uninstallプラグイン削除voidいいえ
content:beforeSaveコンテンツ保存前変更されたコンテンツまたは voidいいえ
content:afterSaveコンテンツ保存後voidいいえ
content:beforeDeleteコンテンツ削除前false でキャンセル、それ以外は許可いいえ
content:afterDeleteコンテンツ削除後voidいいえ
media:beforeUploadファイルアップロード前変更されたファイル情報または voidいいえ
media:afterUploadファイルアップロード後voidいいえ
cronスケジュールタスク発火voidいいえ
email:beforeSendメール配信前変更されたメッセージ、false、または voidいいえ
email:deliverトランスポート経由でメール配信voidはい
email:afterSendメール配信後voidいいえ
comment:beforeCreateコメント保存前変更されたイベント、false、または voidいいえ
comment:moderateコメントステータス決定{ status, reason? }はい
comment:afterCreateコメント保存後voidいいえ
comment:afterModerate管理者がコメントステータス変更voidいいえ
page:metadataページレンダリング貢献または nullいいえ
page:fragmentsページレンダリング (信頼済み)貢献または nullいいえ

完全なイベントタイプとハンドラーシグネチャについては、フックリファレンスを参照してください。