creating-plugins について
EmDash プラグイン開発者向けのガイドで、サンドボックス型とネイティブ型の形式を扱います。権限宣言、実行境界、本番動作を検証するテストを重視しています。
主な機能
- 01サンドボックス型とネイティブ型
- 02プラグインの雛形とバンドル
- 03ホスト API 権限の宣言
- 04ルート、フック、管理 UI
- 05本番境界のテスト
活用シーン
- 01サンドボックス型プラグインの作成
- 02必要なプラグイン権限の宣言
- 03フック、ルート、実行時動作のテスト
MCP 対応
MCP ツールを、明示的な権限と Zod 入力スキーマを持つプライベート JSON ルートに関連付ける方法を説明します。MCP ツールの追加には管理者の再承認が必要です。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ emdash-cms/ emdash --skill creating-pluginsインストール後、Claude Code で/creating-pluginsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/admin-ui.mdapi-routes.mdblock-kit.mdcomments.mdcontent.mdhooks.mdmedia.mdportable-text-blocks.mdpublishing.mdsandbox-boundaries.mdstorage.mdtaxonomies-and-redirects.md詳細
EmDash プラグインの作成
意図した実行モードに対応する API を使用してビルドします。このスキルの例と食い違う場合は、ソースの種類と本番環境の境界に関するテストを優先してください。
形式を選択する
| 形式 | ランタイムソース | 管理 UI | 配布 |
|---|---|---|---|
| サンドボックス型 | src/plugin.ts が SandboxedPlugin をデフォルトエクスポート | Block Kit ページ、ウィジェット、保存済みエントリーパネル、アクション | プラグイン CLI とレジストリ |
| ネイティブ | definePlugin() / createPlugin() | React、Block Kit、Astro コンポーネント | 信頼されたサイト依存関係のみ |
ホストプロセスへのアクセス、React による管理コード、Astro のレンダリングコンポーネント、生のページフラグメント、またはカスタム Portable Text ブロック定義が必要な機能でない限り、サンドボックス型プラグインを使用してください。ネイティブプラグインはサイトの権限で実行され、レジストリからインストールすることはできません。
サンドボックス型プラグインのスキャフォールド
pnpm dlx @emdash-cms/plugin-cli init my-plugin
cd my-plugin
pnpm install
pnpm run test
マニフェストは、ID と信頼に関する契約です。ランタイムフックとルートは src/plugin.ts に記述します。CLI はディスクリプター、マニフェスト、バンドルを生成します。別個のディスクリプターファクトリーや sandbox-entry.ts は作成しないでください。
import type { SandboxedPlugin } from "emdash/plugin";
const plugin: SandboxedPlugin = {
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
};
export default plugin;
作成用の型は emdash/plugin から import type を使ってインポートします。CLI は、emdash/plugin の pluginRoute() や pluginResponse()、および emdash/client の portableTextToMarkdown() などの値のインポートをバンドルします。サンドボックス型のランタイムコードでは Web API を使用できますが、Node.js の組み込みモジュールは使用できません。
アクセスを宣言する
emdash-plugin.jsonc ですべてのホスト API を宣言してください。権限の追加、ルートの公開、または MCP ツールの追加には、管理者による改めての承認が必要です。
| 機能 | 付与される権限 |
|---|---|
schema:read | 公開コレクションとフィールド定義 |
admin.editor-draft:read | 明示的なエディター操作後の、選択された未保存のフィールド値 |
admin.editor-draft:patch | エディターレビュー用に提案された、ホスト検証済みの未保存フィールド変更 |
content:read | コンテンツの識別情報、翻訳、公開済みの公開 URL |
content:revisions:read | 保持されているリビジョンデータ。コンテンツの読み取りを暗黙的に含む |
content:write | 作成、更新、削除、翻訳の作成。読み取りを暗黙的に含む |
content:publish | リビジョンで保護された公開、非公開、スケジュール、スケジュール解除。読み取りを暗黙的に含む |
content:restore | リビジョンで保護された読み取りと、ゴミ箱内コンテンツの復元 |
hooks.content-policy:register | 公開前、スケジュール前、非公開前のポリシーフック |
taxonomies:read | タクソノミー定義、用語、エントリの割り当て |
taxonomies:write | 用語の作成と割り当ての差分。読み取りを暗黙的に含む |
bylines:read | 公開バイラインプロフィールと、単一または一括のエントリクレジット |
redirects:read | バージョン管理されたリダイレクトの検査 |
redirects:write | バージョン管理されたリダイレクトの作成、更新、削除。読み取りを暗黙的に含む |
comments:read | 保存された、ゴミ箱にないコメントとその個人データ |
comments:moderate | 期待ステータスによるモデレーション。読み取りを暗黙的に含む |
media:read | 準備完了メディアのメタデータと認証済みアセット URL |
media:bytes:read | 制限付きメディアバイトとコンテンツハッシュ |
media:metadata:write | 代替テキスト、キャプション、フォーカルポイントの更新 |
media:write | アップロードと削除。メディアの読み取りを暗黙的に含む |
network:request | allowedHosts に制限された ctx.http.fetch() |
network:request:unrestricted | ホストリストなしの ctx.http.fetch() |
users:read | ユーザーディレクトリの検索。コメントフックでも必要 |
email:send | トランスポートが設定されている場合のメール配信 |
hooks.email-transport:register | 排他的な email:deliver フック |
hooks.email-events:register | メールの前後フック |
hooks.page-fragments:register | 信頼済み専用のページフラグメント。サンドボックス登録から除外 |
設定、KV、宣言されたストレージ、ロギング、cron スケジューリングはプラグインスコープであり、ケイパビリティは不要です。ユーザー設定には ctx.settings を、内部のキーと値の状態には ctx.kv を、クエリ可能なレコードには宣言された ctx.storage.<collection> を使用してください。
使用する API の詳細なリファレンスを参照してください:
ルート、HTTP、MCP
ルートはデフォルトで非公開であり、認証、宣言された RBAC 権限、トークンスコープ、または CSRF 対策が必要に応じて求められます。公開ルートはインターネットに公開されるため、インストール時に明示的な同意が必要です。
宣言されていないルートでは、従来の JSON/クエリエンベロープが維持されます。ホスト側で解析を強制するには、メソッドと本文モードを宣言してください。pluginResponse() は、生のレスポンスを宣言したルートでのみ使用してください。送信する ctx.http.fetch() のレスポンスと、宣言されたルートの本文はバッファーに読み込まれ、サイズが制限されます。
MCP ツールでは、明示的な権限と Zod 入力スキーマを指定した非公開 JSON ルートを参照します。元に戻すことが難しい操作には destructive: true を指定してください。API ルートと MCP ツールをお読みください。
フック
サンドボックス化されたフックは、信頼されたフックと同じ優先順位、依存関係、タイムアウト、エラーポリシー、有効化、排他的プロバイダー、機能のパイプラインに組み込まれます。公開ポリシーフックは、公開権限を付与することなく、アクションの起点と実行者を識別します。ctx.comments を通じて呼び出されたコメントモデレーションは、プラグイン起点であることを報告し、通常の after フックを一度実行します。フックをお読みください。
宣言型管理 UI
サンドボックス化されたページとウィジェットは、検証済みの Block Kit を返します。構造化リンクはホストが解決するターゲットを使用し、routeCtx.ui にはホストが証明したロケール、方向、サーフェスが含まれます。外部画像には、一致するネットワーク権限が必要です。
保存済みエントリーのパネルとアクションは、非公開ルートを参照します。通常のパネル読み込みで受け取るのは、ホストが再読み込みした保存済みの ID とバージョンだけです。明示的な操作で選択済みの未保存フィールドを受け取る場合や、フィールド全体を対象とするアトミックなパッチを提案する場合は、admin.editor-draft:read または admin.editor-draft:patch を追加し、拡張機能レベルのコレクションおよびフィールドセレクターも指定してください。パッチ権限には読み取り権限が含まれません。ホストは受け付けたパッチをプレビューし、フォームを変更済みとしてマークします。自動的に保存することはありません。保存済みのコンテンツは、機能によってアクセスが制御される ctx.content を通じて読み取ってください。
宣言型フィールドウィジェットでは、現在、サポート対象の Block Kit 要素を組み合わせて JSON 値を作成できます。カスタム Portable Text ブロックと Astro レンダーコンポーネントは、引き続きネイティブのみで利用できます。管理 UI、Block Kit、Portable Text ブロックをお読みください。
ランナーの同等性
Cloudflare Worker Loader と Node/workerd は、プラグイン単位のブリッジを介して同じバンドルを実行します。どちらも、バイナリのバイト列を保持したうえで、ctx.http.fetch() からバッファーに読み込まれた WHATWG レスポンスを返します。リクエスト本文とレスポンス本文のデコードは 8 MiB に制限されます。一方のラッパーで見つかる追加メソッドではなく、エクスポートされた型に基づいて実装してください。
ここに記載されていない API を利用する設計を行う前に、サンドボックスの境界をお読みください。
本番環境の境界をテストする
フック、ルート、マニフェスト、機能、KV、設定、ストレージのトランスポートをすばやくテストするには、createPluginTestHost() を使用してください。実際のコンテンツアクション、プラグインの有効化、メディア、コメント、リダイレクト、スケジューリング、再起動、認可、CSRF、キャッシュ、Block Kit の検証、保存済みエントリーの拡張機能をテストする必要がある場合は、createPluginRuntimeTestHost() を使用してください。
ランタイムフィクスチャは、フックを実行せずに状態を設定します。ランタイムアクションは本番環境の境界を呼び出し、インスペクターは観測可能な状態を読み取ります。送信する HTTP レスポンスは host.http.respond() でキューに追加し、リクエストは host.http.requests() で確認してください。使用後はすべてのホストを破棄してください。Node/workerd の同等性テストは、ランナーに依存する動作に限って追加してください。