Configuración

En esta página

Los plugins en sandbox almacenan su configuración en el almacén KV por plugin y renderizan la interfaz de edición como una página Block Kit. El formulario admin.settingsSchema autogenerado que los plugins nativos pueden usar no está disponible en el sandbox; en su lugar, se describe el formulario en JSON y se sirve desde una ruta.

Es un poco más de trabajo que settingsSchema, pero todo sucede a través de la misma maquinaria que el plugin ya usa para hooks y rutas; no hay nada adicional que aprender.

El almacén KV

Cada plugin obtiene un almacén de clave-valor privado accesible como ctx.kv en cualquier hook o ruta. Es el lugar canónico para configuración y cualquier otro estado persistente pequeño:

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 es por plugin: las claves que escribe se almacenan bajo su ID de plugin y no son visibles para otros plugins.

Lectura y escritura

// Lectura
const enabled = await ctx.kv.get<boolean>("settings:enabled");
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");

// Escritura
await ctx.kv.set("settings:lastSync", new Date().toISOString());
await ctx.kv.set("state:cache", { data: items, expiry: Date.now() + 3600000 });

// Eliminación
const deleted = await ctx.kv.delete("state:tempData");

// Listar por prefijo
const allSettings = await ctx.kv.list("settings:");
// → [{ key: "settings:enabled", value: true }, ...]

Convenciones de nombres de claves

Use prefijos para mantener separados diferentes tipos de valores. La convención en los plugins EmDash:

PrefijoPropósitoEjemplo
settings:Preferencias configurables por el usuariosettings:apiKey
state:Estado interno del pluginstate:lastSync
cache:Datos en cachécache:results
// Prefijos claros
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);

// Evite claves sin prefijo
await ctx.kv.set("url", url);

UI de configuración en Block Kit

Los plugins en sandbox describen su página de configuración como Block Kit. El administrador envía una interacción page_load a una ruta en su plugin (convencionalmente routes.admin), y el plugin devuelve una descripción JSON del formulario. Cuando el usuario hace clic en Guardar, el administrador envía una interacción block_action o form_submit de vuelta; el plugin escribe en KV y devuelve bloques actualizados.

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);
		}
	}
}

Para conectar la página de configuración en la barra lateral de administración, declárela en el descriptor:

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

EmDash enruta automáticamente las interacciones page_load para esa ruta a su ruta admin.

Consulte Block Kit para el conjunto completo de tipos de bloques, campos de formulario, campos condicionales y los ayudantes del constructor @emdash-cms/blocks.

Valores secretos

El campo secret_input de Block Kit se renderiza como una entrada enmascarada. Trate cualquier valor que el usuario ingrese allí con cuidado:

{
	type: "secret_input",
	action_id: "apiKey",
	label: "API key",
	// No inicialice initial_value con el secreto real: pase una cadena vacía o un centinela,
	// y sobrescriba solo cuando el usuario ingrese un valor no vacío.
	initial_value: "",
}

Al guardar, omita cadenas vacías para evitar borrar el secreto existente en cada guardado:

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);
	}
	// ... otros campos
}

Valores predeterminados

Las lecturas de KV devuelven null para claves que no se han escrito. Pase valores predeterminados en el sitio de lectura:

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

O persista valores predeterminados durante la instalación:

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

La compensación es que plugin:install se ejecuta una vez por instalación. Si envía una nueva configuración en una versión posterior, solo las instalaciones nuevas ven el valor predeterminado; las instalaciones existentes necesitan una migración en plugin:activate (idempotente: escribir solo si falta) o seguir usando el respaldo en tiempo de lectura.

Configuración vs. almacenamiento vs. KV

Caso de usoMecanismo
Preferencias editables por administradorctx.kv con prefijo settings: + página Block Kit
Estado interno del pluginctx.kv con prefijo state:
Colecciones de documentos (consultas)ctx.storage

KV es para valores pequeños con clave por cadena: configuración, cursores de sincronización, cálculos en caché. Sin consultas, sin índices.

Storage es para colecciones de documentos con consultas indexadas: envíos de formularios, registros de auditoría, cualquier cosa donde desee filtrar, paginar o contar.

Diseño de almacenamiento

Los valores de KV residen en la tabla _options con claves con espacio de nombres de plugin. Su código usa settings:apiKey; EmDash lo almacena como plugin:<your-plugin-id>:settings:apiKey. El prefijo se agrega automáticamente y evita que un plugin lea o sobrescriba los datos de KV de otro plugin.

Plugins nativos: settingsSchema

Si está escribiendo un plugin nativo (porque necesita páginas de administración React o componentes PT), puede declarar un esquema de configuración directamente dentro de definePlugin() y dejar que EmDash genere automáticamente el formulario. Consulte Plugins nativos para ese camino.