沙盒插件將其設定儲存在每個插件的 KV 儲存中,並將編輯 UI 渲染為 Block Kit 頁面:您用 JSON 描述表單並從路由提供它。
一切都透過插件已經用於鉤子和路由的同一機制進行——沒有額外的東西需要學習。
KV 儲存
每個插件都獲得一個私有鍵值儲存,可在任何鉤子或路由中以 ctx.kv 的形式存取。它是設定和任何其他小型持久狀態的規範位置:
interface KVAccess {
get<T>(key: string): Promise<T | null>;
set(key: string, value: unknown): Promise<void>;
delete(key: string): Promise<boolean>;
list(prefix?: string): Promise<Array<{ key: string; value: unknown }>>;
}
KV 是按插件劃分的——您寫入的鍵儲存在您的插件 ID 下,對其他插件不可見。
讀取和寫入
// Read
const enabled = await ctx.kv.get<boolean>("settings:enabled");
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
// Write
await ctx.kv.set("settings:lastSync", new Date().toISOString());
await ctx.kv.set("state:cache", { data: items, expiry: Date.now() + 3600000 });
// Delete
const deleted = await ctx.kv.delete("state:tempData");
// List by prefix
const allSettings = await ctx.kv.list("settings:");
// → [{ key: "settings:enabled", value: true }, ...]
鍵命名慣例
使用前綴來分離不同類型的值。EmDash 插件中的慣例:
| 前綴 | 用途 | 範例 |
|---|---|---|
settings: | 使用者可配置的偏好設定 | settings:apiKey |
state: | 內部插件狀態 | state:lastSync |
cache: | 快取資料 | cache:results |
// Clear prefixes
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// Avoid bare keys
await ctx.kv.set("url", url);
Block Kit 中的設定 UI
沙盒插件將其設定頁面描述為 Block Kit。管理員向您插件的路由(通常為 routes.admin)發送 page_load 互動,插件返回表單的 JSON 描述。當使用者點擊儲存時,管理員發送 block_action 或 form_submit 互動;插件寫入 KV 並返回更新的區塊。
import type { PluginContext, SandboxedPlugin } from "emdash/plugin";
interface BlockInteraction {
type: "page_load" | "block_action" | "form_submit";
page?: string;
action_id?: string;
values?: Record<string, unknown>;
}
export default {
routes: {
admin: {
handler: async (routeCtx, ctx) => {
const interaction = routeCtx.input as BlockInteraction;
if (interaction.type === "page_load" && interaction.page === "/settings") {
return renderSettings(ctx);
}
if (interaction.type === "form_submit" && interaction.action_id === "save") {
await saveSettings(ctx, interaction.values ?? {});
return {
...(await renderSettings(ctx)),
toast: { message: "Settings saved", type: "success" },
};
}
return { blocks: [] };
},
},
},
} satisfies SandboxedPlugin;
async function renderSettings(ctx: PluginContext) {
const apiKey = (await ctx.kv.get<string>("settings:apiKey")) ?? "";
const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;
return {
blocks: [
{ type: "header", text: "Plugin settings" },
{
type: "form",
block_id: "settings",
fields: [
{
type: "secret_input",
action_id: "apiKey",
label: "API key",
initial_value: apiKey,
},
{
type: "toggle",
action_id: "enabled",
label: "Enabled",
initial_value: enabled,
},
{
type: "number_input",
action_id: "maxItems",
label: "Max items",
min: 1,
max: 1000,
initial_value: maxItems,
},
],
submit: { label: "Save", action_id: "save" },
},
],
};
}
async function saveSettings(ctx: PluginContext, values: Record<string, unknown>) {
for (const [key, value] of Object.entries(values)) {
if (value !== undefined) {
await ctx.kv.set(`settings:${key}`, value);
}
}
}
要將設定頁面連接到管理側邊欄,在清單中宣告它:
"admin": {
"pages": [{ "path": "/settings", "label": "Settings", "icon": "settings" }]
}
EmDash 自動將該路徑的 page_load 互動路由到您的 admin 路由。
有關區塊類型、表單欄位、條件欄位和 @emdash-cms/blocks 建構器助手的完整集合,請參閱 Block Kit。
秘密值
Block Kit 的 secret_input 欄位渲染為遮罩輸入。小心處理使用者在那裡輸入的任何值:
{
type: "secret_input",
action_id: "apiKey",
label: "API key",
// Don't seed initial_value with the real secret — pass an empty string or a sentinel,
// and only overwrite when the user enters a non-empty value.
initial_value: "",
}
儲存時,跳過空字串以避免在每次儲存時清除現有秘密:
async function saveSettings(ctx: PluginContext, values: Record<string, unknown>) {
if (typeof values.apiKey === "string" && values.apiKey.length > 0) {
await ctx.kv.set("settings:apiKey", values.apiKey);
}
// ... other fields
}
預設值
KV 讀取對於尚未寫入的鍵返回 null。在讀取位置傳遞預設值:
const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;
或在安裝期間持久化預設值:
hooks: {
"plugin:install": async (_event, ctx) => {
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
},
},
權衡是 plugin:install 每次安裝執行一次。如果您在後續版本中發布新設定,只有新安裝才能看到預設值——現有安裝需要在 plugin:activate 中進行遷移(冪等:僅在缺失時寫入)或繼續使用讀取時回退。
設定 vs. storage vs. KV
| 用例 | 機制 |
|---|---|
| 管理員可編輯的偏好設定 | 帶有 settings: 前綴的 ctx.kv + Block Kit 頁面 |
| 內部插件狀態 | 帶有 state: 前綴的 ctx.kv |
| 文件集合(查詢) | ctx.storage |
KV 用於由字串鍵入的小值——設定、同步游標、快取計算。無查詢,無索引。
Storage 用於具有索引查詢的文件集合——表單提交、稽核日誌,任何您想要過濾、分頁或計數的內容。
儲存佈局
KV 值位於 _options 表中,具有插件命名空間的鍵。您的程式碼使用 settings:apiKey;EmDash 將其儲存為 plugin:<your-plugin-id>:settings:apiKey。前綴自動新增,防止一個插件讀取或覆寫另一個插件的 KV 資料。
原生插件:settingsSchema
如果您正在編寫原生插件(因為您需要 React 管理頁面或 PT 元件),您可以直接在 definePlugin() 內部宣告設定模式,並讓 EmDash 自動生成表單。有關該路徑,請參閱原生插件。