Descripción general del sistema de plugins

En esta página

El sistema de plugins de EmDash te permite extender el CMS sin modificar el código principal. Los plugins pueden engancharse a eventos del ciclo de vida del contenido, almacenar sus propios datos, exponer configuraciones a los administradores y añadir interfaz de usuario personalizada al panel de administración.

Filosofía de diseño

Los plugins de EmDash vienen en dos sabores: sandboxed y nativos. Los plugins sandboxed se ejecutan en workers V8 aislados y pueden instalarse desde el mercado con un clic. Los plugins nativos se ejecutan en proceso y se configuran en código.

Prefiere plugins sandboxed. Pueden ser instalados, actualizados y eliminados desde la interfaz de administración sin tocar código o redesplegar. Usa plugins nativos solo cuando necesites características que requieran integración en tiempo de compilación (páginas admin de React, componentes de renderizado Portable Text o inyección de fragmentos de página).

Principios clave:

  • Sandbox-first — Diseña para el sandbox; usa el modo nativo solo cuando lo necesites
  • Declarativo — Los hooks, el almacenamiento y las rutas se declaran en el momento de la definición, no se registran dinámicamente
  • Type-safe — Soporte completo de TypeScript con objetos de contexto tipados
  • Basado en capacidades — Los plugins declaran lo que necesitan; el sandbox lo aplica

Lo que los plugins pueden hacer

Engancharse a eventos

Ejecutar código antes o después de guardados de contenido, subidas de medios y eventos del ciclo de vida de plugins.

Almacenar datos

Persistir datos específicos del plugin en colecciones indexadas sin escribir migraciones de base de datos.

Exponer configuración

Declarar un esquema de configuración y obtener una interfaz admin autogenerada para la configuración.

Añadir páginas admin

Crear páginas admin personalizadas y widgets de panel con componentes React.

Crear rutas API

Exponer endpoints para la interfaz admin de tu plugin o integraciones externas.

Hacer solicitudes HTTP

Llamar a APIs externas con restricciones de host declaradas para seguridad.

Arquitectura de plugin

Cada plugin se crea con definePlugin():

import { definePlugin } from "emdash";

export default definePlugin({
	id: "my-plugin",
	version: "1.0.0",

	// APIs a las que el plugin necesita acceder
	capabilities: ["read:content", "network:fetch"],

	// Hosts a los que el plugin puede hacer solicitudes HTTP
	allowedHosts: ["api.example.com"],

	// Colecciones de almacenamiento persistente
	storage: {
		entries: {
			indexes: ["userId", "createdAt"],
		},
	},

	// Manejadores de eventos
	hooks: {
		"content:afterSave": async (event, ctx) => {
			ctx.log.info("Content saved", { id: event.content.id });
		},
	},

	// Endpoints REST API
	routes: {
		status: {
			handler: async (ctx) => ({ ok: true }),
		},
	},

	// Configuración de interfaz admin
	admin: {
		settingsSchema: {
			apiKey: { type: "secret", label: "API Key" },
		},
		pages: [{ path: "/dashboard", label: "Dashboard" }],
		widgets: [{ id: "status", size: "half" }],
	},
});

Contexto del plugin

Cada hook y manejador de ruta recibe un objeto PluginContext con acceso a:

PropiedadDescripciónDisponibilidad
ctx.storageColecciones de documentos del pluginSiempre (si se declara)
ctx.kvAlmacén clave-valor para configuración y estadoSiempre
ctx.contentLectura/escritura de contenido del sitioCon read:content o write:content
ctx.mediaLectura/escritura de archivos multimediaCon read:media o write:media
ctx.httpCliente HTTP para solicitudes externasCon network:fetch
ctx.logLogger estructurado (debug, info, warn, error)Siempre
ctx.pluginMetadatos del plugin (id, version)Siempre
ctx.siteInfo del sitio: name, url, localeSiempre
ctx.url()Generar URLs absolutas desde rutasSiempre
ctx.usersLeer info de usuarios: get(), getByEmail(), list()Con read:users
ctx.cronProgramar tareas: schedule(), cancel(), list()Siempre
ctx.emailEnviar correo: send()Con email:send + proveedor configurado

La forma del contexto es idéntica en todos los hooks y rutas. Las propiedades protegidas por capacidad solo están presentes cuando el plugin declara la capacidad requerida.

Capacidades

Las capacidades determinan qué APIs están disponibles en el contexto del plugin:

CapacidadOtorga acceso a
read:contentctx.content.get(), ctx.content.list()
write:contentctx.content.create(), ctx.content.update(), ctx.content.delete()
read:mediactx.media.get(), ctx.media.list()
write:mediactx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete()
network:fetchctx.http.fetch() (restringido a allowedHosts)
network:fetch:anyctx.http.fetch() (sin restricciones — para URLs configuradas por usuario)
read:usersctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (requiere plugin proveedor)
email:provideRegistrar hook exclusivo email:deliver (proveedor transporte)
email:interceptRegistrar hooks email:beforeSend / email:afterSend
page:injectRegistrar hooks page:metadata / page:fragments

Registro

Registra plugins en tu configuración Astro:

import { defineConfig } from "astro/config";
import { emdash } from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
import auditLogPlugin from "@emdash-cms/plugin-audit-log";

export default defineConfig({
	integrations: [
		emdash({
			plugins: [seoPlugin({ generateSitemap: true }), auditLogPlugin({ retentionDays: 90 })],
		}),
	],
});

Los plugins se resuelven en tiempo de compilación. El orden importa para hooks con la misma prioridad — los plugins anteriores en el array se ejecutan primero.

Modos de ejecución

EmDash soporta dos modos de ejecución de plugins:

ModoDescripciónPlataforma
SandboxedWorkers V8 aislados con límites forzadosSolo Cloudflare
NativoEn proceso con acceso completoCualquiera

En modo sandboxed, las capacidades se aplican a nivel de runtime — los plugins solo pueden acceder a lo que declaran. En modo nativo, las capacidades son consultivas y los plugins tienen acceso completo al proceso.

Próximos pasos