Paramètres

Sur cette page

Les plugins sandboxés stockent les paramètres spécifiques au site dans leur magasin clé-valeur (KV) privé. Une page d’administration Block Kit charge les valeurs actuelles, accepte les modifications, les valide et les écrit via ctx.kv.

Lire et écrire des valeurs KV

Chaque hook et route reçoit cette interface KV sur ctx :

interface KVAccess {
	get<T>(key: string): Promise<T | null>;
	getVersioned<T>(key: string): Promise<{ value: T; revision: string } | null>;
	compareAndSet(key: string, expectedRevision: string | null, value: unknown):
		Promise<{ applied: true; revision: string } | { applied: false }>;
	compareAndDelete(key: string, expectedRevision: string): Promise<{ applied: boolean }>;
	set(key: string, value: unknown): Promise<void>;
	delete(key: string): Promise<boolean>;
	list(prefix?: string): Promise<Array<{ key: string; value: unknown }>>;
}

Le KV est cloisonné par plugin. Deux plugins peuvent utiliser la même clé sans lire ni écraser les valeurs de l’autre.

Lorsque des requêtes concurrentes peuvent modifier la même clé, utilisez les écritures conditionnelles pour rejeter les mises à jour basées sur une révision périmée. Les mêmes méthodes fonctionnent dans les plugins natifs et les plugins sandboxés.

Utilisez des préfixes pour séparer les paramètres utilisateur de l’état interne et des valeurs en cache :

PréfixeObjectifExemple
settings:Valeurs configurables par l’utilisateursettings:apiKey
state:État interne persistantstate:lastSync
cache:Données calculées ou distantes réutilisablescache:feed

Les appels suivants couvrent les opérations KV :

const enabled = await ctx.kv.get<boolean>("settings:enabled");
await ctx.kv.set("state:lastSync", new Date().toISOString());
const deleted = await ctx.kv.delete("cache:feed");
const allSettings = await ctx.kv.list("settings:");

get renvoie null lorsque la clé n’existe pas. list renvoie les clés sans le préfixe de namespace interne de plugin d’EmDash.

Ajouter une page de paramètres

Déclarez la page dans emdash-plugin.jsonc pour qu’elle apparaisse dans la navigation d’administration du plugin :

"admin": {
	"pages": [{ "path": "/settings", "label": "Settings", "icon": "settings" }],
}

Le plugin doit également fournir une route privée nommée admin. EmDash envoie page_load lorsque la page s’ouvre et form_submit lorsque l’utilisateur soumet le formulaire.

Ajoutez @emdash-cms/blocks et zod pour utiliser le type de réponse et valider les interactions :

pnpm add @emdash-cms/blocks zod

La route suivante charge trois valeurs et n’écrit que les champs de formulaire validés :

import type { BlockResponse } from "@emdash-cms/blocks";
import type { PluginContext, SandboxedPlugin } from "emdash/plugin";
import { z } from "zod";

const interactionSchema = z.discriminatedUnion("type", [
	z.object({ type: z.literal("page_load"), page: z.string() }),
	z.object({
		type: z.literal("form_submit"),
		action_id: z.string(),
		block_id: z.string().optional(),
		values: z.object({
			apiKey: z.string().optional(),
			enabled: z.boolean(),
			maxItems: z.number().int().min(1).max(1000),
		}),
	}),
	z.object({
		type: z.literal("block_action"),
		action_id: z.string(),
		block_id: z.string().optional(),
		value: z.unknown().optional(),
	}),
]);

const plugin: SandboxedPlugin = {
	routes: {
		admin: {
			handler: async (routeCtx, ctx) => {
				const parsed = interactionSchema.safeParse(routeCtx.input);
				if (!parsed.success) return { blocks: [] };
				const interaction = parsed.data;

				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: [] };
			},
		},
	},
};

export default plugin;

async function renderSettings(ctx: PluginContext): Promise<BlockResponse> {
	const apiKeyConfigured = (await ctx.kv.get<string>("settings:apiKey")) !== null;
	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",
						has_value: apiKeyConfigured,
					},
					{
						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: { apiKey?: string; enabled: boolean; maxItems: number },
) {
	if (values.apiKey) await ctx.kv.set("settings:apiKey", values.apiKey);
	await ctx.kv.set("settings:enabled", values.enabled);
	await ctx.kv.set("settings:maxItems", values.maxItems);
}

Les valeurs soumises omettent le secret jusqu’à ce que l’utilisateur le modifie, et peuvent contenir une chaîne vide si l’utilisateur met le focus sur le champ et l’efface. saveSettings n’écrit une nouvelle clé API que lorsque la chaîne soumise n’est pas vide. La page utilise has_value pour indiquer qu’une valeur enregistrée existe sans renvoyer la valeur au navigateur.

Block Kit est la référence canonique pour les interactions, les blocs, les éléments de formulaire, les constructeurs et les champs conditionnels.

Valeurs secrètes

N’utilisez le KV que lorsque ce modèle de stockage est acceptable pour l’identifiant. Si un secret doit provenir d’un magasin de secrets de déploiement et ne doit pas être écrit dans la base de données EmDash, utilisez un plugin natif ou un service externe de gestion des identifiants. Les plugins sandboxés ne peuvent pas lire les variables d’environnement du processus hôte ni les bindings de la plateforme.

Fournissez une action séparée et délibérée si les utilisateurs doivent supprimer un secret. Traiter un champ masqué vide comme une suppression peut effacer un identifiant fonctionnel lorsqu’un utilisateur enregistre un paramètre non lié.

Valeurs par défaut et mises à jour

Appliquez des valeurs par défaut lors de la lecture d’une clé afin que les installations existantes reçoivent un nouveau paramètre sans migration :

const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;

Vous pouvez persister les valeurs initiales lors de l’installation :

hooks: {
	"plugin:install": async (_event, ctx) => {
		await ctx.kv.set("settings:enabled", true);
		await ctx.kv.set("settings:maxItems", 100);
	},
},

plugin:install ne s’exécute que pour une nouvelle installation. Lorsqu’une version ultérieure ajoute un paramètre, les sites existants ne l’exécutent pas à nouveau. Conservez le repli à la lecture, ou initialisez la clé manquante de manière idempotente pendant plugin:activate.

Choisir KV ou storage

DonnéesUtilisation
Petites valeurs configurables par l’utilisateurctx.kv avec le préfixe settings:
Petit état interne ou curseursctx.kv avec le préfixe state:
Enregistrements interrogeables comme les soumissions ou les journauxUne collection ctx.storage déclarée
Contenu modifié via l’éditeur EmDash standardUne collection de contenu du site

Le KV prend en charge l’accès direct par clé et le listage par préfixe, mais ne dispose pas de requêtes sur les champs ni d’index. Storage fournit des collections de documents avec filtrage indexé, tri, comptage et pagination.

Les plugins natifs peuvent à la place déclarer admin.settingsSchema dans definePlugin() et laisser EmDash générer le formulaire. Voir Votre premier plugin natif pour ce format.