Configurações

Nesta página

Plugins em sandbox armazenam suas configurações no armazenamento KV por plugin e renderizam a interface de edição como uma página Block Kit. O formulário admin.settingsSchema gerado automaticamente que plugins nativos podem usar não está disponível no sandbox — em vez disso, você descreve o formulário em JSON e o serve de uma rota.

É um pouco mais de trabalho do que settingsSchema, mas tudo acontece através do mesmo mecanismo que o plugin já usa para hooks e rotas — não há nada extra para aprender.

O armazenamento KV

Cada plugin obtém um armazenamento de chave-valor privado acessível como ctx.kv em qualquer hook ou rota. É o local canônico para configurações e qualquer outro pequeno estado 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 é por plugin — chaves que você escreve são armazenadas sob seu ID de plugin e não são visíveis para outros plugins.

Leitura e escrita

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

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

// Exclusão
const deleted = await ctx.kv.delete("state:tempData");

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

Convenções de nomenclatura de chaves

Use prefixos para manter diferentes tipos de valores separados. A convenção nos plugins EmDash:

PrefixoPropósitoExemplo
settings:Preferências configuráveis pelo usuáriosettings:apiKey
state:Estado interno do pluginstate:lastSync
cache:Dados em cachecache:results
// Prefixos claros
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);

// Evite chaves sem prefixo
await ctx.kv.set("url", url);

Interface de configurações no Block Kit

Plugins em sandbox descrevem sua página de configurações como Block Kit. O administrador envia uma interação page_load para uma rota no seu plugin (convencionalmente routes.admin), e o plugin retorna uma descrição JSON do formulário. Quando o usuário clica em Salvar, o administrador envia uma interação block_action ou form_submit de volta; o plugin escreve no KV e retorna blocos atualizados.

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 a página de configurações na barra lateral de administração, declare-a no descritor:

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

EmDash roteia automaticamente as interações page_load para esse caminho para sua rota admin.

Veja Block Kit para o conjunto completo de tipos de bloco, campos de formulário, campos condicionais e os auxiliares do construtor @emdash-cms/blocks.

Valores secretos

O campo secret_input do Block Kit é renderizado como uma entrada mascarada. Trate qualquer valor que o usuário insira lá com cuidado:

{
	type: "secret_input",
	action_id: "apiKey",
	label: "API key",
	// Não inicialize initial_value com o segredo real — passe uma string vazia ou um sentinela,
	// e sobrescreva apenas quando o usuário inserir um valor não vazio.
	initial_value: "",
}

Ao salvar, pule strings vazias para evitar limpar o segredo existente em cada salvamento:

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

Valores padrão

Leituras de KV retornam null para chaves que não foram escritas. Passe valores padrão no local de leitura:

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

Ou persista valores padrão durante a instalação:

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

O trade-off é que plugin:install é executado uma vez por instalação. Se você enviar uma nova configuração em uma versão posterior, apenas novas instalações veem o valor padrão — instalações existentes precisam de uma migração em plugin:activate (idempotente: escrever apenas se estiver faltando) ou continuar usando o fallback no momento da leitura.

Configurações vs. armazenamento vs. KV

Caso de usoMecanismo
Preferências editáveis pelo administradorctx.kv com prefixo settings: + página Block Kit
Estado interno do pluginctx.kv com prefixo state:
Coleções de documentos (consultas)ctx.storage

KV é para valores pequenos indexados por uma string — configurações, cursores de sincronização, cálculos em cache. Sem consultas, sem índices.

Storage é para coleções de documentos com consultas indexadas — envios de formulários, logs de auditoria, qualquer coisa onde você queira filtrar, paginar ou contar.

Layout de armazenamento

Valores KV residem na tabela _options com chaves com namespace de plugin. Seu código usa settings:apiKey; EmDash o armazena como plugin:<your-plugin-id>:settings:apiKey. O prefixo é adicionado automaticamente e impede que um plugin leia ou sobrescreva os dados KV de outro plugin.

Plugins nativos: settingsSchema

Se você está escrevendo um plugin nativo (porque precisa de páginas de administração React ou componentes PT), pode declarar um schema de configurações diretamente dentro de definePlugin() e deixar EmDash gerar automaticamente o formulário. Veja Plugins nativos para esse caminho.