Los plugins pueden exponer rutas de API para su UI admin e integraciones externas. Las rutas se montan bajo /_emdash/api/plugins/<slug>/<nombre-ruta> y se ejecutan dentro del runtime del sandbox con el mismo PluginContext que reciben los hooks.
Esta página cubre plugins sandboxed. La superficie de API para plugins nativos es la misma; la única diferencia es la firma del handler — ver Plugins nativos.
Definir rutas
import type { SandboxedPlugin } from "emdash/plugin";
import { z } from "astro/zod";
export default {
routes: {
status: {
handler: async (_routeCtx, ctx) => {
return { ok: true, plugin: ctx.plugin.id };
},
},
submissions: {
input: z.object({
formId: z.string().optional(),
limit: z.number().default(50),
cursor: z.string().optional(),
}),
handler: async (routeCtx, ctx) => {
const { formId, limit, cursor } = routeCtx.input;
const result = await ctx.storage.submissions.query({
where: formId ? { formId } : undefined,
orderBy: { createdAt: "desc" },
limit, cursor,
});
return result;
},
},
},
} satisfies SandboxedPlugin;
Los handlers de rutas sandboxed toman dos argumentos: (routeCtx, ctx).
Filtrar campos de contenido indexados
const result = await ctx.content.list("items", {
where: {
fieldFilters: {
priority: { in: ["urgent", "high"] },
score: { gte: 80 },
resolved: false,
},
},
});
URLs de rutas
| Plugin id | Nombre de ruta | URL |
|---|---|---|
forms | status | /_emdash/api/plugins/forms/status |
forms | submissions | /_emdash/api/plugins/forms/submissions |
Autenticación y CSRF
Las rutas de plugin están autenticadas por defecto. Usa permission para una verificación más específica. Para rutas públicas, marca public: true.
El llamador autenticado
En rutas privadas, routeCtx.user es el usuario autenticado.
Cachear respuestas públicas
routes: {
catalog: {
public: true,
cacheControl: "public, max-age=60, stale-while-revalidate=300",
handler: async (ctx) => listProducts(ctx),
},
},
Exponer una ruta como herramienta MCP
mcp: {
tools: {
createEvent: {
description: "Create a calendar event when the user asks to add one.",
route: "events/create",
input: createEventInput,
output: z.object({ id: z.string() }),
destructive: false,
},
},
},
Validación de entrada
input acepta un esquema Zod.
Entrada de query string (GET/HEAD/DELETE)
Usa z.coerce para campos no string.
Valores de retorno
Devuelve cualquier valor serializable a JSON. El dispatcher lo envuelve en { success: true, data: <tu valor> }.
Errores
Lanza un error para una respuesta de error. Para un código de estado específico, lanza un Response.
Métodos HTTP
Las rutas responden a todos los métodos. Bifurca en routeCtx.request.method.
Patrones comunes
Configuración vía KV
Lista paginada
Proxy de API externa
Llamar rutas desde la UI admin
import { usePluginAPI } from "@emdash-cms/admin";
function SettingsPage() {
const api = usePluginAPI();
const handleSave = async (settings) => { await api.post("settings/save", settings); };
}
Llamar rutas desde handlers de cola y programados
Usa withEmDashRuntime() de emdash/middleware.
Llamar rutas externamente
Las rutas públicas son llamables directamente. Las rutas privadas necesitan credenciales de sesión o un token API.
Referencia del contexto de ruta
interface SandboxedRouteContext {
input: unknown;
request: SandboxedRequest;
requestMeta?: unknown;
user?: UserInfo;
}
interface PluginContext {
plugin: { id: string; version: string };
storage: PluginStorage;
kv: KVAccess;
log: LogAccess;
site: SiteInfo;
content?: ContentAccess;
taxonomies?: TaxonomyAccess;
media?: MediaAccess;
http?: HttpAccess;
users?: UserAccess;
email?: EmailAccess;
}