I plugin sandboxed memorizzano le loro impostazioni nel KV store per plugin e rendono l’UI di modifica come una pagina Block Kit: descrivi il modulo in JSON e lo servi da una route.
Tutto avviene attraverso la stessa macchina che il plugin già utilizza per hooks e routes – non c’è niente di extra da imparare.
Il KV store
Ogni plugin ottiene un archivio chiave-valore privato accessibile come ctx.kv in qualsiasi hook o route. È il luogo canonico per le impostazioni e qualsiasi altro piccolo stato persistente:
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 è per plugin – le chiavi che scrivi sono memorizzate sotto il tuo plugin id e non sono visibili ad altri plugin.
Lettura e scrittura
// 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 }, ...]
Convenzioni di denominazione delle chiavi
Usa i prefissi per mantenere separati diversi tipi di valori. La convenzione nei plugin EmDash:
| Prefisso | Scopo | Esempio |
|---|---|---|
settings: | Preferenze configurabili dall’utente | settings:apiKey |
state: | Stato interno del plugin | state:lastSync |
cache: | Dati in 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);
UI delle impostazioni in Block Kit
I plugin sandboxed descrivono la loro pagina di impostazioni come Block Kit. L’admin invia un’interazione page_load a una route sul tuo plugin (convenzionalmente routes.admin), e il plugin restituisce una descrizione JSON del modulo. Quando l’utente fa clic su Salva, l’admin invia un’interazione block_action o form_submit indietro; il plugin scrive su KV e restituisce blocchi aggiornati.
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);
}
}
}
Per collegare la pagina delle impostazioni nella barra laterale dell’admin, dichiarala nel manifesto:
"admin": {
"pages": [{ "path": "/settings", "label": "Settings", "icon": "settings" }]
}
EmDash instrada automaticamente le interazioni page_load per quel percorso alla tua route admin.
Vedi Block Kit per l’insieme completo di tipi di blocchi, campi modulo, campi condizionali e gli helper di costruzione @emdash-cms/blocks.
Valori segreti
Il campo secret_input di Block Kit viene renderizzato come un input mascherato. Tratta con cura qualsiasi valore che l’utente inserisce lì:
{
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: "",
}
Durante il salvataggio, salta le stringhe vuote per evitare di cancellare il segreto esistente ad ogni salvataggio:
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
}
Valori predefiniti
Le letture KV restituiscono null per le chiavi che non sono state scritte. Passa valori predefiniti nel sito di lettura:
const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;
Oppure persisti i valori predefiniti durante l’installazione:
hooks: {
"plugin:install": async (_event, ctx) => {
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
},
},
Il compromesso è che plugin:install viene eseguito una volta per installazione. Se spedisci una nuova impostazione in una versione successiva, solo le nuove installazioni vedono il valore predefinito – le installazioni esistenti hanno bisogno di una migrazione in plugin:activate (idempotente: scrivi solo se mancante) o di continuare a usare il fallback al momento della lettura.
Impostazioni vs. storage vs. KV
| Caso d’uso | Meccanismo |
|---|---|
| Preferenze modificabili dall’admin | ctx.kv con prefisso settings: + pagina Block Kit |
| Stato interno del plugin | ctx.kv con prefisso state: |
| Collezioni di documenti (query) | ctx.storage |
KV è per piccoli valori identificati da una stringa – impostazioni, cursori di sincronizzazione, calcoli in cache. Nessuna query, nessun indice.
Storage è per collezioni di documenti con query indicizzate – invii di moduli, log di audit, qualsiasi cosa in cui vuoi filtrare, paginare o contare.
Layout dello storage
I valori KV vivono nella tabella _options con chiavi con namespace del plugin. Il tuo codice usa settings:apiKey; EmDash lo memorizza come plugin:<your-plugin-id>:settings:apiKey. Il prefisso viene aggiunto automaticamente e impedisce a un plugin di leggere o sovrascrivere i dati KV di un altro.
Plugin nativi: settingsSchema
Se stai scrivendo un plugin nativo (perché hai bisogno di pagine admin React o componenti PT), puoi dichiarare uno schema di impostazioni direttamente dentro definePlugin() e lasciare che EmDash generi automaticamente il modulo. Vedi Plugin nativi per quel percorso.