フックはプラグインがイベントに応答してコードを実行できるようにします。すべてのフックはイベントオブジェクトとプラグインコンテキストを受け取り、プラグイン定義時に宣言されます — ランタイムでの動的な登録はありません。
このページではサンドボックスプラグインについて説明します。フックはネイティブプラグインでも同様に動作します。ネイティブプラグインはさらに page:fragments を登録できます。
フックシグネチャ
すべてのフックハンドラーは2つの引数を取ります:
async (event, ctx) => ReturnType;
event— 発生したことに関するデータ(コンテンツの保存、メディアのアップロード、ライフサイクルの遷移など)ctx— ストレージ、KV、ロギング、および機能制御APIを持つPluginContext
デフォルトエクスポートの satisfies SandboxedPlugin がフック名(完全な正規イベントタイプ)から event を推論し、ctx を PluginContext として推論するため、ハンドラーにはパラメータアノテーションが不要です。ヘルパーでイベントタイプを名前で参照するには、emdash/plugin からインポートしてください。
フック設定
フックはシンプルなハンドラーとして、または設定オブジェクトでラップして宣言できます:
シンプル
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved");
},
}, 完全な設定
hooks: {
"content:afterSave": {
priority: 100,
timeout: 5000,
dependencies: ["audit-log"],
errorPolicy: "continue",
handler: async (event, ctx) => {
ctx.log.info("Content saved");
},
},
}, 設定オプション
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
priority | number | 100 | 実行順序。小さい数値が先に実行されます。 |
timeout | number | 5000 | 最大実行時間(ミリ秒)。 |
dependencies | string[] | [] | このフックの前に実行する必要があるプラグインID。 |
errorPolicy | "abort" | "continue" | "abort" | エラー時にパイプラインを停止するかどうか。 |
exclusive | boolean | false | アクティブなプロバイダーは1つのプラグインのみ。email:deliver と comment:moderate で使用。 |
handler | function | — | フックハンドラー関数。必須。 |
ライフサイクルフック
プラグインのインストール、アクティベーション、非アクティベーション、削除時に実行されます。
plugin:install
プラグインが初めてサイトに追加されたときに一度だけ実行されます。
"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
プラグインが有効化されたとき(インストール後または再有効化時)に実行されます。
"plugin:activate": async (_event, ctx) => {
ctx.log.info("Plugin activated");
},
イベント: {} — 戻り値: Promise<void>
plugin:deactivate
プラグインが無効化されたとき(ただし削除されていない)に実行されます。
"plugin:deactivate": async (_event, ctx) => {
ctx.log.info("Plugin deactivated");
},
イベント: {} — 戻り値: Promise<void>
plugin:uninstall
プラグインがサイトから削除されたときに実行されます。
"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>
コンテンツフック
サイトコンテンツの作成、更新、削除操作時に実行されます。
content:beforeSave
コンテンツが保存される前に実行されます。変更されたコンテンツを返すか、変更しない場合は void を返します。キャンセルするには例外をスローします。
"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;
},
イベント: { content, collection, isNew } — 戻り値: 変更されたコンテンツまたは void。
content:afterSave
コンテンツが正常に保存された後に実行されます。通知、ロギング、外部同期などの副作用に使用します。
"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 }),
});
}
},
イベント: { content, collection, isNew } — 戻り値: Promise<void>
content:beforeDelete
コンテンツが削除される前に実行されます。キャンセルするには false を返し、許可するには true または void を返します。
"content:beforeDelete": async (event, ctx) => {
if (event.collection === "pages" && event.id === "home") {
ctx.log.warn("Cannot delete home page");
return false;
}
return true;
},
イベント: { id, collection } — 戻り値: boolean | void
content:afterDelete
コンテンツが正常に削除された後に実行されます。
"content:afterDelete": async (event, ctx) => {
await ctx.storage.cache.delete(`${event.collection}:${event.id}`);
},
イベント: { id, collection } — 戻り値: Promise<void>
content:afterPublish
コンテンツが下書きから公開に昇格された後に実行されます。content:read 機能が必要です。
イベント: { content, collection } — 戻り値: Promise<void>
content:afterUnpublish
コンテンツが公開から下書きに戻された後に実行されます。content:read 機能が必要です。
イベント: { content, collection } — 戻り値: Promise<void>
content:afterRestore
削除されたコンテンツが復元された後に実行されます。content:read 機能が必要です。
イベント: { content, collection } — 戻り値: Promise<void>
content:afterSchedule
コンテンツが将来の公開にスケジュールされた後に実行されます。content:read 機能が必要です。
イベント: { content, collection } — 戻り値: Promise<void>
content:afterUnschedule
スケジュールされたコンテンツのスケジュールが解除された後に実行されます。content:read 機能が必要です。
イベント: { content, collection } — 戻り値: Promise<void>
メディアフック
media:beforeUpload
ファイルがアップロードされる前に実行されます。変更されたファイルメタデータを返すか、キャンセルするには例外をスローします。
"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}` };
},
イベント: { file: { name, type, size } } — 戻り値: 変更されたファイルまたは void
media:afterUpload
ファイルが正常にアップロードされた後に実行されます。
イベント: { media: { id, filename, mimeType, size, url, createdAt } } — 戻り値: Promise<void>
パブリックページフック
これらはプラグインがレンダリングされたパブリックページに貢献できるようにします。テンプレートは emdash/ui の <EmDashHead>、<EmDashBodyStart>、<EmDashBodyEnd> コンポーネントを含めることでオプトインします。
page:metadata
<head> に型付けされたメタデータを提供します — メタタグ、OpenGraphプロパティ、許可された <link> rel、JSON-LD。サンドボックスプラグインとネイティブプラグインの両方で利用可能です。 Coreは貢献を検証、重複排除、レンダリングします。プラグインは構造化データを返し、生の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.pageTitle ?? event.page.title,
description: event.page.description,
},
};
},
イベント:
{
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 };
}
}
戻り値: 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(存在する場合) |
各重複排除キーに対して最初の貢献が優先されます。Link rel はセキュリティロックされた許可リスト(canonical、alternate、author、license、nlweb、site.standard.document)に制限されています。href はHTTPまたはHTTPSである必要があります。
page:fragments
ページの挿入ポイントに生のHTML、スクリプト、またはスタイルシートを提供します。ネイティブプラグインのみ。
サンドボックスプラグインはこのフックを使用できません。その出力は訪問者のブラウザでファーストパーティコードとして実行され、サンドボックス境界の外側になるためです。サンドボックスセーフなページ貢献には page:metadata を使用してください。この機能が必要な場合は ネイティブプラグイン: ページフラグメント を参照してください。
フック実行順序
フックは以下の順序で実行されます:
priority値が低いフックが最初に実行されます。- 同じ優先度の場合、プラグインの登録順序で実行されます。
dependenciesを持つフックは、それらのプラグインが完了するまで待機します。
// プラグイン A
"content:afterSave": { priority: 50, handler: async () => {} }
// プラグイン B
"content:afterSave": { priority: 100, handler: async () => {} }
// プラグイン C
"content:afterSave": {
priority: 200,
dependencies: ["plugin-a"], // 優先度が通常は後になるとしても A を待つ
handler: async () => {},
}
エラー処理
フックが例外をスローするかタイムアウトした場合:
errorPolicy: "abort"— パイプライン全体が停止し、元のオペレーションが失敗する可能性があります。errorPolicy: "continue"— エラーはログに記録され、残りのフックは実行を続けます。
"content:afterSave": {
timeout: 5000,
errorPolicy: "continue",
handler: async (event, ctx) => {
await ctx.http!.fetch("https://unreliable-api.com/notify");
},
},
タイムアウト
フックのデフォルトタイムアウトは5000msです。遅い処理にはタイムアウトを増やしてください:
"content:afterSave": {
timeout: 30000,
handler: async (event, ctx) => {
// 長時間実行されるオペレーション
},
},
フックリファレンス
| フック | トリガー | 戻り値 | 排他的 |
|---|---|---|---|
plugin:install | 初回プラグインインストール | void | いいえ |
plugin:activate | プラグイン有効化 | void | いいえ |
plugin:deactivate | プラグイン無効化 | void | いいえ |
plugin:uninstall | プラグイン削除 | void | いいえ |
content:beforeSave | コンテンツ保存前 | 変更されたコンテンツまたは void | いいえ |
content:afterSave | コンテンツ保存後 | void | いいえ |
content:beforeDelete | コンテンツ削除前 | false でキャンセル、それ以外は許可 | いいえ |
content:afterDelete | コンテンツ削除後 | void | いいえ |
content:afterPublish | コンテンツ公開後 | void | いいえ |
content:afterUnpublish | コンテンツ非公開後 | void | いいえ |
content:afterRestore | コンテンツ復元後 | void | いいえ |
content:afterSchedule | コンテンツスケジュール後 | void | いいえ |
content:afterUnschedule | スケジュール解除後 | 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 | いいえ |
完全なイベントタイプとハンドラーシグネチャについては フックリファレンス を参照してください。