フック

このページ

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

このページではサンドボックスプラグインについて説明します。フックはネイティブプラグインでも同様に動作します。ネイティブプラグインはさらに page:fragments を登録できます。

フックシグネチャ

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

async (event, ctx) => ReturnType;
  • event — 発生したことに関するデータ(コンテンツの保存、メディアのアップロード、ライフサイクルの遷移など)
  • ctx — ストレージ、KV、ロギング、および機能制御APIを持つ PluginContext

デフォルトエクスポートの satisfies SandboxedPlugin がフック名(完全な正規イベントタイプ)から event を推論し、ctxPluginContext として推論するため、ハンドラーにはパラメータアノテーションが不要です。ヘルパーでイベントタイプを名前で参照するには、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");
		},
	},
},

設定オプション

オプションデフォルト説明
prioritynumber100実行順序。小さい数値が先に実行されます。
timeoutnumber5000最大実行時間(ミリ秒)。
dependenciesstring[][]このフックの前に実行する必要があるプラグインID。
errorPolicy"abort" | "continue""abort"エラー時にパイプラインを停止するかどうか。
exclusivebooleanfalseアクティブなプロバイダーは1つのプラグインのみ。email:delivercomment:moderate で使用。
handlerfunctionフックハンドラー関数。必須。

ライフサイクルフック

プラグインのインストール、アクティベーション、非アクティベーション、削除時に実行されます。

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 はセキュリティロックされた許可リスト(canonicalalternateauthorlicensenlwebsite.standard.document)に制限されています。href はHTTPまたはHTTPSである必要があります。

page:fragments

ページの挿入ポイントに生のHTML、スクリプト、またはスタイルシートを提供します。ネイティブプラグインのみ。

サンドボックスプラグインはこのフックを使用できません。その出力は訪問者のブラウザでファーストパーティコードとして実行され、サンドボックス境界の外側になるためです。サンドボックスセーフなページ貢献には page:metadata を使用してください。この機能が必要な場合は ネイティブプラグイン: ページフラグメント を参照してください。

フック実行順序

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

  1. priority 値が低いフックが最初に実行されます。
  2. 同じ優先度の場合、プラグインの登録順序で実行されます。
  3. 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いいえ

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