Einstellungen

Auf dieser Seite

Sandboxed Plugins speichern ihre Einstellungen im plugin-spezifischen KV-Speicher und rendern die Bearbeitungsoberfläche als Block Kit-Seite. Das automatisch generierte admin.settingsSchema-Formular, das native Plugins verwenden können, ist in der Sandbox nicht verfügbar — stattdessen beschreiben Sie das Formular in JSON und stellen es über eine Route bereit.

Es ist etwas mehr Arbeit als settingsSchema, aber alles geschieht über denselben Mechanismus, den das Plugin bereits für Hooks und Routen verwendet — es gibt nichts zusätzlich zu lernen.

Der KV-Speicher

Jedes Plugin erhält einen privaten Key-Value-Speicher, der in jedem Hook oder jeder Route als ctx.kv zugänglich ist. Dies ist der kanonische Ort für Einstellungen und andere kleine persistente Zustände:

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 ist plugin-spezifisch — Schlüssel, die Sie schreiben, werden unter Ihrer Plugin-ID gespeichert und sind für andere Plugins nicht sichtbar.

Lesen und Schreiben

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

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

// Löschen
const deleted = await ctx.kv.delete("state:tempData");

// Nach Präfix auflisten
const allSettings = await ctx.kv.list("settings:");
// → [{ key: "settings:enabled", value: true }, ...]

Benennungskonventionen für Schlüssel

Verwenden Sie Präfixe, um verschiedene Arten von Werten zu trennen. Die Konvention bei EmDash-Plugins:

PräfixZweckBeispiel
settings:Benutzerkonfigurierbare Einstellungensettings:apiKey
state:Interner Plugin-Zustandstate:lastSync
cache:Zwischengespeicherte Datencache:results
// Klare Präfixe
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);

// Vermeiden Sie Schlüssel ohne Präfix
await ctx.kv.set("url", url);

Einstellungs-UI in Block Kit

Sandboxed Plugins beschreiben ihre Einstellungsseite als Block Kit. Der Admin sendet eine page_load-Interaktion an eine Route Ihres Plugins (konventionell routes.admin), und das Plugin gibt eine JSON-Beschreibung des Formulars zurück. Wenn der Benutzer auf Speichern klickt, sendet der Admin eine block_action- oder form_submit-Interaktion zurück; das Plugin schreibt in KV und gibt aktualisierte Blöcke zurück.

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

Um die Einstellungsseite in die Admin-Seitenleiste einzubinden, deklarieren Sie sie im Deskriptor:

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

EmDash routet page_load-Interaktionen für diesen Pfad automatisch zu Ihrer admin-Route.

Siehe Block Kit für den vollständigen Satz von Blocktypen, Formularfeldern, bedingten Feldern und den @emdash-cms/blocks-Builder-Helfern.

Geheime Werte

Das secret_input-Feld von Block Kit wird als maskierte Eingabe gerendert. Behandeln Sie jeden Wert, den der Benutzer dort eingibt, mit Vorsicht:

{
	type: "secret_input",
	action_id: "apiKey",
	label: "API key",
	// Initialisieren Sie initial_value nicht mit dem echten Geheimnis — übergeben Sie einen leeren String oder einen Sentinel,
	// und überschreiben Sie nur, wenn der Benutzer einen nicht-leeren Wert eingibt.
	initial_value: "",
}

Beim Speichern leere Strings überspringen, um zu vermeiden, dass das vorhandene Geheimnis bei jedem Speichern gelöscht wird:

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);
	}
	// ... andere Felder
}

Standardwerte

KV-Lesevorgänge geben null für Schlüssel zurück, die nicht geschrieben wurden. Übergeben Sie Standardwerte am Leseort:

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

Oder persistieren Sie Standardwerte während der Installation:

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

Der Kompromiss besteht darin, dass plugin:install nur einmal pro Installation ausgeführt wird. Wenn Sie in einer späteren Version eine neue Einstellung ausliefern, sehen nur neue Installationen den Standardwert — bestehende Installationen benötigen entweder eine Migration in plugin:activate (idempotent: nur schreiben, wenn fehlend) oder müssen den Fallback zur Lesezeit weiter verwenden.

Einstellungen vs. Storage vs. KV

AnwendungsfallMechanismus
Vom Admin bearbeitbare Einstellungenctx.kv mit settings:-Präfix + Block Kit-Seite
Interner Plugin-Zustandctx.kv mit state:-Präfix
Dokumentensammlungen (Abfragen)ctx.storage

KV ist für kleine Werte, die durch einen String indexiert sind — Einstellungen, Sync-Cursor, zwischengespeicherte Berechnungen. Keine Abfragen, keine Indizes.

Storage ist für Dokumentensammlungen mit indizierten Abfragen — Formularübermittlungen, Audit-Logs, alles, wo Sie filtern, paginieren oder zählen möchten.

Speicherlayout

KV-Werte leben in der _options-Tabelle mit plugin-namespaceten Schlüsseln. Ihr Code verwendet settings:apiKey; EmDash speichert es als plugin:<your-plugin-id>:settings:apiKey. Das Präfix wird automatisch hinzugefügt und verhindert, dass ein Plugin die KV-Daten eines anderen Plugins liest oder überschreibt.

Native Plugins: settingsSchema

Wenn Sie ein natives Plugin schreiben (weil Sie React-Admin-Seiten oder PT-Komponenten benötigen), können Sie ein Einstellungsschema direkt innerhalb von definePlugin() deklarieren und EmDash das Formular automatisch generieren lassen. Siehe Native Plugins für diesen Weg.