Les plugins en sandbox stockent leurs paramètres dans le magasin KV par plugin et rendent l’interface d’édition sous forme de page Block Kit. Le formulaire admin.settingsSchema généré automatiquement que les plugins natifs peuvent utiliser n’est pas disponible dans le sandbox — à la place, vous décrivez le formulaire en JSON et le servez depuis une route.
C’est un peu plus de travail que settingsSchema, mais tout se passe via le même mécanisme que le plugin utilise déjà pour les hooks et les routes — il n’y a rien de supplémentaire à apprendre.
Le magasin KV
Chaque plugin dispose d’un magasin clé-valeur privé accessible via ctx.kv dans n’importe quel hook ou route. C’est l’emplacement canonique pour les paramètres et tout autre petit état persistant :
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 est par plugin — les clés que vous écrivez sont stockées sous votre ID de plugin et ne sont pas visibles par les autres plugins.
Lecture et écriture
// Lecture
const enabled = await ctx.kv.get<boolean>("settings:enabled");
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
// Écriture
await ctx.kv.set("settings:lastSync", new Date().toISOString());
await ctx.kv.set("state:cache", { data: items, expiry: Date.now() + 3600000 });
// Suppression
const deleted = await ctx.kv.delete("state:tempData");
// Lister par préfixe
const allSettings = await ctx.kv.list("settings:");
// → [{ key: "settings:enabled", value: true }, ...]
Conventions de nommage des clés
Utilisez des préfixes pour séparer les différents types de valeurs. La convention dans les plugins EmDash :
| Préfixe | Objectif | Exemple |
|---|---|---|
settings: | Préférences configurables par l’utilisateur | settings:apiKey |
state: | État interne du plugin | state:lastSync |
cache: | Données en cache | cache:results |
// Préfixes clairs
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// Évitez les clés sans préfixe
await ctx.kv.set("url", url);
Interface de paramètres dans Block Kit
Les plugins en sandbox décrivent leur page de paramètres en tant que Block Kit. L’administrateur envoie une interaction page_load à une route de votre plugin (conventionnellement routes.admin), et le plugin renvoie une description JSON du formulaire. Lorsque l’utilisateur clique sur Enregistrer, l’administrateur renvoie une interaction block_action ou form_submit ; le plugin écrit dans KV et renvoie des blocs mis à jour.
import { definePlugin } from "emdash";
import type { PluginContext } from "emdash";
interface BlockInteraction {
type: "page_load" | "block_action" | "form_submit";
page?: string;
action_id?: string;
values?: Record<string, unknown>;
}
export default definePlugin({
routes: {
admin: {
handler: async (routeCtx, ctx: PluginContext) => {
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: [] };
},
},
},
});
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);
}
}
}
Pour intégrer la page de paramètres dans la barre latérale d’administration, déclarez-la dans le descripteur :
adminPages: [{ path: "/settings", label: "Settings", icon: "settings" }],
EmDash achemine automatiquement les interactions page_load pour ce chemin vers votre route admin.
Consultez Block Kit pour l’ensemble complet des types de blocs, des champs de formulaire, des champs conditionnels et des aides au constructeur @emdash-cms/blocks.
Valeurs secrètes
Le champ secret_input de Block Kit se rend sous forme d’entrée masquée. Traitez toute valeur que l’utilisateur y entre avec précaution :
{
type: "secret_input",
action_id: "apiKey",
label: "API key",
// N'initialisez pas initial_value avec le vrai secret — passez une chaîne vide ou une sentinelle,
// et n'écrasez que lorsque l'utilisateur entre une valeur non vide.
initial_value: "",
}
Lors de l’enregistrement, ignorez les chaînes vides pour éviter d’effacer le secret existant à chaque enregistrement :
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);
}
// ... autres champs
}
Valeurs par défaut
Les lectures KV renvoient null pour les clés qui n’ont pas été écrites. Passez les valeurs par défaut au site de lecture :
const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;
Ou persistez les valeurs par défaut lors de l’installation :
hooks: {
"plugin:install": async (_event, ctx) => {
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
},
},
Le compromis est que plugin:install s’exécute une fois par installation. Si vous livrez un nouveau paramètre dans une version ultérieure, seules les nouvelles installations voient la valeur par défaut — les installations existantes ont besoin d’une migration dans plugin:activate (idempotente : écrire uniquement si manquant) ou de continuer à utiliser le repli au moment de la lecture.
Paramètres vs. stockage vs. KV
| Cas d’usage | Mécanisme |
|---|---|
| Préférences modifiables par l’administrateur | ctx.kv avec préfixe settings: + page Block Kit |
| État interne du plugin | ctx.kv avec préfixe state: |
| Collections de documents (requêtes) | ctx.storage |
KV est destiné aux petites valeurs indexées par chaîne — paramètres, curseurs de synchronisation, calculs mis en cache. Pas de requêtes, pas d’index.
Storage est destiné aux collections de documents avec requêtes indexées — soumissions de formulaires, journaux d’audit, tout ce pour quoi vous voulez filtrer, paginer ou compter.
Disposition du stockage
Les valeurs KV résident dans la table _options avec des clés à espace de noms de plugin. Votre code utilise settings:apiKey ; EmDash le stocke comme plugin:<your-plugin-id>:settings:apiKey. Le préfixe est ajouté automatiquement et empêche un plugin de lire ou d’écraser les données KV d’un autre plugin.
Plugins natifs : settingsSchema
Si vous écrivez un plugin natif (parce que vous avez besoin de pages d’administration React ou de composants PT), vous pouvez déclarer un schéma de paramètres directement dans definePlugin() et laisser EmDash générer automatiquement le formulaire. Consultez Plugins natifs pour ce chemin.