Los plugins necesitan configuración: claves API, feature flags, preferencias de visualización. EmDash ofrece dos mecanismos: un esquema de ajustes para opciones configurables en el admin y un almacén KV para acceso programático.
Esquema de ajustes
Declara un esquema de ajustes en admin.settingsSchema para generar automáticamente la UI de administración:
import { definePlugin } from "emdash";
export default definePlugin({
id: "seo",
version: "1.0.0",
admin: {
settingsSchema: {
siteTitle: {
type: "string",
label: "Site Title",
description: "Used in title tags and meta",
default: "",
},
maxTitleLength: {
type: "number",
label: "Max Title Length",
description: "Characters before truncation",
default: 60,
min: 30,
max: 100,
},
generateSitemap: {
type: "boolean",
label: "Generate Sitemap",
description: "Automatically generate sitemap.xml",
default: true,
},
defaultRobots: {
type: "select",
label: "Default Robots",
options: [
{ value: "index,follow", label: "Index & Follow" },
{ value: "noindex,follow", label: "No Index, Follow" },
{ value: "noindex,nofollow", label: "No Index, No Follow" },
],
default: "index,follow",
},
apiKey: {
type: "secret",
label: "API Key",
description: "Encrypted at rest",
},
},
},
});
EmDash genera un formulario de ajustes en la sección de administración del plugin. Los usuarios editan la configuración sin tocar código.
Tipos de campo
String
Entrada de texto para cadenas de una o varias líneas.
siteTitle: {
type: "string",
label: "Site Title",
description: "Optional help text",
default: "My Site",
multiline: false // Set true for textarea
}
Number
Entrada numérica con límites opcionales min/max.
maxItems: {
type: "number",
label: "Maximum Items",
default: 100,
min: 1,
max: 1000
}
Boolean
Interruptor para valores verdadero/falso.
enabled: {
type: "boolean",
label: "Enabled",
description: "Turn this feature on or off",
default: true
}
Select
Lista desplegable con opciones predefinidas.
theme: {
type: "select",
label: "Theme",
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "auto", label: "System" }
],
default: "auto"
}
Secret
Campo cifrado para valores sensibles como claves API. Nunca se envía al cliente tras guardar.
apiKey: {
type: "secret",
label: "API Key",
description: "Stored encrypted"
}
Acceder a los ajustes
Lee los ajustes en hooks y rutas mediante ctx.kv:
"content:beforeSave": async (event, ctx) => {
// Read a setting
const maxLength = await ctx.kv.get<number>("settings:maxTitleLength");
const apiKey = await ctx.kv.get<string>("settings:apiKey");
// Use defaults if not set
const limit = maxLength ?? 60;
ctx.log.info("Using max length", { limit });
return event.content;
}
Los ajustes se almacenan por convención con el prefijo settings:. Así se distinguen los valores configurables por el usuario del estado interno del plugin.
API del almacén KV
El almacén KV (ctx.kv) es un almacén clave-valor de propósito general para datos del plugin:
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 }>>;
}
Leer valores
// Get a single value
const enabled = await ctx.kv.get<boolean>("settings:enabled");
// Get with type
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
Escribir valores
// Set a value
await ctx.kv.set("settings:lastSync", new Date().toISOString());
// Set complex values
await ctx.kv.set("state:cache", {
data: items,
expiry: Date.now() + 3600000,
});
Listar valores
// List all settings
const settings = await ctx.kv.list("settings:");
// Returns: [{ key: "settings:enabled", value: true }, ...]
// List all plugin keys
const all = await ctx.kv.list();
Eliminar valores
const deleted = await ctx.kv.delete("state:tempData");
// Returns true if key existed
Convenciones de nombres de claves
Usa prefijos para organizar los datos KV:
| Prefijo | Propósito | Ejemplo |
|---|---|---|
settings: | Preferencias configurables por el usuario | settings:apiKey |
state: | Estado interno del plugin | state:lastSync |
cache: | Datos en caché | cache:results |
// Good: clear prefixes
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// Avoid: no prefix, unclear purpose
await ctx.kv.set("url", url);
Ajustes frente a Storage frente a KV
Elige el mecanismo de almacenamiento adecuado:
| Caso de uso | Mecanismo |
|---|---|
| Preferencias editables en el admin | admin.settingsSchema + ctx.kv con settings: |
| Estado interno del plugin | ctx.kv con state: |
| Colecciones de documentos | ctx.storage |
Los ajustes son valores configurables por el usuario: cosas que un administrador podría cambiar. Obtienen una UI generada automáticamente.
El KV es para estado interno como marcas de tiempo, cursores de sincronización o cálculos en caché. Sin UI, solo código.
Storage es para colecciones de documentos con consultas indexadas: envíos de formularios, registros de auditoría, etc.
Cargar ajustes en rutas
Las rutas API pueden exponer ajustes a componentes de la UI de administración:
routes: {
settings: {
handler: async (ctx) => {
const settings = await ctx.kv.list("settings:");
const result: Record<string, unknown> = {};
for (const entry of settings) {
const key = entry.key.replace("settings:", "");
result[key] = entry.value;
}
return result;
}
},
"settings/save": {
handler: async (ctx) => {
const input = ctx.input as Record<string, unknown>;
for (const [key, value] of Object.entries(input)) {
if (value !== undefined) {
await ctx.kv.set(`settings:${key}`, value);
}
}
return { success: true };
}
}
}
Valores por defecto
Los ajustes de settingsSchema no se persisten automáticamente. Son valores por defecto en la UI de administración. Tu código debe manejar valores ausentes:
"content:afterSave": async (event, ctx) => {
// Always provide a fallback
const enabled = await ctx.kv.get<boolean>("settings:enabled") ?? true;
const maxItems = await ctx.kv.get<number>("settings:maxItems") ?? 100;
if (!enabled) return;
// ...
}
Alternativamente, persiste los valores por defecto en plugin:install:
hooks: {
"plugin:install": async (_event, ctx) => {
// Persist schema defaults
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
}
}
Implementación del almacenamiento
Los valores KV se guardan en la tabla _options con claves con espacio de nombres del plugin:
INSERT INTO _options (name, value) VALUES
('plugin:seo:settings:siteTitle', '"My Site"'),
('plugin:seo:settings:maxTitleLength', '60');
El prefijo plugin:seo: se añade automáticamente. Tu código usa settings:siteTitle, y EmDash lo almacena como plugin:seo:settings:siteTitle.
Así los plugins no sobrescriben accidentalmente los datos unos de otros.