关于 creating-plugins
为 EmDash 开发者提供的插件构建指南,涵盖沙箱插件与原生插件,并强调按权限声明、运行边界和生产环境测试进行开发。
主要功能
- 01沙箱与原生插件格式
- 02插件脚手架与打包
- 03声明宿主 API 权限
- 04路由、钩子与管理界面
- 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 构建。如果本技能中的示例与源类型和生产边界测试不一致,应以源类型和生产边界测试为准。
选择格式
| 格式 | 运行时源代码 | 管理界面 | 分发方式 |
|---|---|---|---|
| 沙盒化 | src/plugin.ts 默认导出一个 SandboxedPlugin | Block Kit 页面、小部件、已保存条目面板和操作 | Plugin 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
清单定义了插件的身份和信任契约。运行时钩子和路由位于 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;
使用 import type 从 emdash/plugin 导入编写插件所需的类型。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 调用的评论审核会报告插件来源,并运行一次常规后置钩子。阅读钩子。
声明式管理界面
沙箱化页面和小组件会返回经过验证的 Block Kit。结构化链接使用由宿主解析的目标;routeCtx.ui 携带由宿主认证的区域设置、方向和界面。外部图片需要匹配的网络权限。
已保存条目面板和操作指向私有路由。普通面板加载只会接收由宿主重新加载的已保存身份和版本。若明确的交互必须接收选定的未保存字段或提出原子性的整字段补丁,请添加 admin.editor-draft:read 或 admin.editor-draft:patch,以及扩展级的集合和字段选择器。补丁权限不隐含读取权限。宿主会预览已接受的补丁,将表单标记为已修改,并且绝不会自动保存。通过受能力控制的 ctx.content 读取已保存内容。
声明式字段小组件目前会将受支持的 Block Kit 元素组合成 JSON 值。自定义 Portable Text 区块和 Astro 渲染组件仍仅支持原生实现。阅读管理界面、Block Kit和Portable Text 区块。
运行器一致性
Cloudflare Worker Loader 和 Node/workerd 会通过插件作用域桥接运行相同的 bundle。两者都会从 ctx.http.fetch() 返回经过缓冲的 WHATWG 响应,保留二进制字节,并将解码后的请求/响应正文限制为 8 MiB。请基于导出的类型编写代码,不要依赖某个封装中额外提供的方法。
在围绕此处未列出的 API 进行设计之前,请阅读沙箱边界。
测试生产边界
使用 createPluginTestHost() 快速测试钩子、路由、清单、能力、KV、设置和存储传输。若测试必须覆盖真实内容操作、插件激活、媒体、评论、重定向、调度、重启、授权、CSRF、缓存、Block Kit 验证或已保存条目扩展,请使用 createPluginRuntimeTestHost()。
运行时夹具会建立状态,但不会触发钩子。运行时操作会调用生产边界;检查器会读取可观测状态。使用 host.http.respond() 队列出站 HTTP 响应,并使用 host.http.requests() 检查请求。使用后请处置每个主机。只有在涉及运行器行为时,才添加 Node/workerd 一致性测试。