Topics

Organizar la especificación del plugin de EmDash 1.0: Sandbox, Capability y Registro Distribuido

  • column

El 28 de septiembre de 2026, EmDash, el CMS nativo de Astro de Cloudflare, se lanzó oficialmente en versión 1.0. Posteriormente, el 2 de octubre se lanzó la versión 1.1 y el 7 de octubre la 1.2, con actualizaciones sucesivas en menos de dos semanas.

Escribí sobre cómo actualicé el blog interno "ゆるろぐ" de la versión 0.22 a 1.2 en este artículo. Esta vez, voy a organizar "el mecanismo de plugins", que fue el cambio más significativo en la versión 1.0.

Puedes ver qué tipo de CMS es EmDash en el primer artículo.
En términos generales, es "WordPress reconstruido con Astro + Cloudflare".

¿Cómo cambiaron los complementos en 1.0?

En la era 0.x, los complementos eran básicamente "paquetes npm escritos en el archivo de configuración de Astro", por lo que se ejecutaban en el mismo proceso que el sitio, lo que significaba que los complementos tenían los mismos permisos que el código del sitio y podían acceder a la base de datos y a las variables de entorno, al igual que WordPress.

En 1.0, se agregaron sandbox y registro distribuido.
Esta es una mejora muy significativa.

  • Los complementos se ejecutan por defecto en un entorno aislado y no pueden acceder al contenido, medios, usuarios, secretos ni redes del sitio.
  • El complemento declara los permisos necesarios (Capability) y el administrador del sitio los aprueba durante la instalación.
  • Los permisos aprobados no son solo "declarados en el papel": el tiempo de ejecución los aplica realmente.
  • Los plugins se pueden instalar con un clic desde el panel de administración. El registro de distribución es un registro descentralizado en AT Protocol.

En el blog de Cloudflare, esto se explica en contraste con el modelo de WordPress donde "todos los plugins comparten el mismo proceso".
Cuanto más familiarizado estés con incidentes causados por plugins de WordPress, más relevante te parecerá esta comparación.

Los plugins tienen dos "modos de ejecución"

Lo primero a entender es que el mismo código de plugin tiene dos modos de ejecución: Trusted (confiable) y Sandboxed (en sandbox).

Trusted

Sandboxed

Ubicación de registro

astro.config.mjs en plugins: []

sandboxed: [] o instalar desde el panel de administración

Entorno de ejecución

Mismo proceso que el sitio

V8 isolate aislado

Capability

Solo declaración (no forzada)

Runtime fuerza

Red

Sin restricciones

Bloqueado. Solo los hosts escritos en allowedHosts están disponibles a través de ctx.http

Acceso a datos

Acceso completo a BD

Solo tu almacenamiento / KV y rangos autorizados

Node.js API

Disponible

No disponible (solo Web API)

Límite de recursos

Ninguno

CPU 50 ms, subrequests 10, 30 segundos, aproximadamente 128 MB

Casos de uso recomendados

Código propio, paquetes npm revisados

Terceros, distribución de registro

El concepto de «aislamiento» significa preparar un pequeño entorno de ejecución dedicado para cada complemento y ejecutarlo de forma separada del sitio principal.
En Cloudflare Workers, se utiliza un mecanismo llamado Dynamic Workers para iniciar el Worker del complemento en el momento de la solicitud.
A partir de la versión 1.0, incluso cuando se ejecuta en Node.js, se levanta el mismo tiempo de ejecución que Workers (workerd) en un proceso separado para cargar el complemento, por lo que «el sandbox ya no se limita a Cloudflare».

El punto clave es que el código del complemento es idéntico en ambos modos, y normalmente puedes desarrollar en modo Trusted localmente e implementar en sandbox en producción.

Estructura del complemento: separar archivos en tiempo de compilación y tiempo de ejecución

Los complementos de EmDash se dividen en dos partes que se ejecutan en momentos diferentes.

  1. descriptor (descriptor): metadatos como id, version, capacidades necesarias y definiciones de almacenamiento que se importan desde astro.config.mjs, por lo que se ejecuta dentro de Vite en tiempo de compilación. No debe tener efectos secundarios.
  2. definition(definición): el contenido de hooks y routes. Se ejecuta en el servidor al momento de la solicitud
my-plugin/
├── src/
│   ├── index.ts          # descriptor。astro.config.mjs から import される
│   └── sandbox-entry.ts  # definePlugin({ hooks, routes })。実行時にロードされる
└── package.json          # "." と "./sandbox" を exports で分ける
// src/index.ts
import type { PluginDescriptor } from "emdash";

export function myPlugin(): PluginDescriptor {
	return {
		id: "my-plugin",
		version: "1.0.0",
		format: "standard",
		entrypoint: "@my-org/my-plugin/sandbox",
		capabilities: ["content:read", "network:request"],
		allowedHosts: ["api.example.com"],
		options: {},
	};
}
// src/sandbox-entry.ts
import { definePlugin, type PluginContext } from "emdash";

export default definePlugin({
	hooks: {
		"content:afterSave": async (event, ctx: PluginContext) => {
			ctx.log.info(`saved ${event.collection}/${event.content.id}`);
		},
	},
});

En el lado del descriptor, escribe la ruta del módulo de definition en entrypoint, y EmDash la carga en tiempo de ejecución. Podría parecer tentador consolidarlo en el mismo archivo, pero el código en tiempo de ejecución se incluiría en el build de Vite, así que lo correcto es separarlos.

Standard y Native

Como otro eje, existen dos formatos de plugins: Standard y Native.

  • Standard: la estructura descrita arriba. La interfaz de administración se describe en Block Kit (JSON) que se menciona más adelante. Funciona tanto en Trusted como en Sandboxed, y se puede publicar en el registro.
  • Native: puede incluir una interfaz de administración hecha con React, bloques personalizados para Portable Text (editor de contenido), y componentes de Astro para renderizar en el sitio. A cambio, solo funciona en Trusted y no se puede publicar en el registro

Lo básico es escribir en Standard, y usar Native solo cuando necesitas "agregar bloques personalizados al editor" o "insertar componentes en el HTML del sitio".

En Yuru Log, hemos creado plugins nativos personalizados para tarjetas de productos de Amazon e incrustaciones de video. Ambos agregan bloques dedicados al editor y se renderizan con componentes de Astro; esto no es posible en principio con sandbox (los plugins que se instalan en tiempo de ejecución no pueden traer consigo componentes de Astro que se necesiten en tiempo de compilación).

Capability: la terminología de permisos

El núcleo del sandbox es Capability. El plugin declara "a qué desea acceder" en forma de cadena de texto, y el administrador lo confirma y aprueba en el diálogo de instalación.

En la versión 1.x, el vocabulario se ha reorganizado en la forma :. Aquí hay algunos de los más utilizados.

Capability

Qué puedes hacer

content:read / content:write

Lectura de artículos / Creación, actualización y eliminación

content:publish

Publicación / No publicado / Programado

media:read / media:write

Lectura de medios / Carga y eliminación

network:request

Solicitud HTTP a externos. Solo los hosts escritos en allowedHosts

admin.editor-draft:read / patch

Lectura de contenido no guardado en edición / Sugerencia de correcciones

hooks.content-policy:register

Revisar contenido y bloquear la publicación

También hay taxonomías, comentarios, redirecciones, usuarios, envío de correos electrónicos y más, con un total de 27 tipos.

storage y KV pueden usarse en todo momento sin necesidad de declaración. Están limitados por plugin, por lo que no se pueden ver datos de otros plugins.

Los nombres de la versión 0.x (read:content, network:fetch, etc.) quedaron obsoletos, pero aún funcionan como alias. El runtime los normaliza al nombre actual, por lo que los plugins existentes funcionan sin cambios, pero se emite una advertencia durante la compilación.

Hooks: qué puede reaccionar

El runtime de 1.2.0 define 30 tipos de hooks diferentes. Hay hooks para guardar, publicar, programar y eliminar contenido; cargar medios; enviar correos; enviar y aprobar comentarios; instalar y activar plugins; y ejecución periódica con cron. Ha aumentado considerablemente desde 0.x, y lo más significativo es que los hooks antes y después de la publicación (content:beforePublish / afterPublish) ahora son hooks separados.

Si content:beforeSave ejecuta throw, puedes cancelar el guardado, así que los plugins de validación van aquí. Si quieres detener la publicación en sí, debes declarar hooks.content-policy:register y usar el hook anterior a la publicación.

Lo importante, aunque discreto, es el cambio en 1.2: en el primer inicio después de la actualización, los plugins existentes re-ejecutan sus plugin:install y plugin:activate. Los plugins que inyectan datos iniciales en el hook install deben escribirse de manera que no se rompan si se inyectan dos veces.

Block Kit: panel de administración sin ejecutar JavaScript

En el sandbox, no puedes ejecutar JavaScript de plugins en el navegador. Por eso, el panel de administración se escribe con Block Kit, un formato JSON. El plugin devuelve un array de bloques como «encabezado, tabla, formulario, interruptor», y EmDash se encarga de renderizarlos.

Así es el flujo. Cuando el usuario abre la página del plugin, EmDash envía una solicitud al plugin indicando que la página se abrió, y el plugin devuelve un array de bloques. Cuando el usuario envía el formulario, llega una solicitud con los valores ingresados, el plugin los guarda y devuelve un nuevo array de bloques. Como la interfaz funciona solo con solicitud-respuesta, este diseño se adapta bien a las limitaciones del sandbox.

Además de las páginas del panel de administración y widgets del dashboard, en 1.x también se pueden crear paneles al lado del editor (editorPanels) con Block Kit. Están diseñados para mostrar estado y acciones por artículo en la pantalla de edición. Por ejemplo, mostrar el estado de traducciones, verificar SEO y estilo de texto, inspeccionar enlaces rotos, generar extractos del cuerpo; toda funcionalidad dirigida al «artículo que estás escribiendo ahora» encaja aquí.

Registro distribuido: diseño sin marketplace central

El mecanismo de distribución de plugins también cambió en 1.0. Desde la perspectiva del usuario, solo ves «buscar en el panel de administración e instalar con un clic», pero detrás de escenas se construye sobre AT Protocol, la base de Bluesky, con una estructura un poco diferente.

  • La información del complemento se encuentra en la cuenta del autor. El autor inicia sesión con una cuenta del sistema Bluesky, firma la información del paquete y la versión, y la coloca en el área de datos de su cuenta. No se deposita en el servidor del operador del registro.
  • El registro de Cloudflare es simplemente «uno de los catálogos». plugins.emdashcms.com solo recopila la información que cada autor ha publicado para permitir búsquedas, y cualquiera puede crear un catálogo alternativo recopilando la misma información.
  • Verifica el contenido durante la instalación. EmDash comprueba que el complemento descargado tenga «el mismo contenido que el publicado y firmado por el autor». Verifica que el hash del archivo, el nombre del paquete y la versión, y las capacidades solicitadas coincidan con la información firmada, y si fue compilado por GitHub Actions, también valida ese registro de compilación. Los complementos alterados por el catálogo o modificados se rechazan en este punto.
  • La moderación solo «oculta». El catálogo de Cloudflare utiliza un modelo de IA (Clef) para revisar las descripciones, enlaces e imágenes, y si hay problemas, no los muestra en el catálogo. Sin embargo, no puede alterar el contenido del complemento ni quitarle la propiedad al autor.

En otras palabras, el modelo funciona así: «Si el operador del registro desaparece, la distribución continúa si la información permanece en la cuenta del autor» y «el operador puede ocultar del catálogo, pero no puede alterar».
La característica es que está diseñado bajo la premisa de no tener un mercado centralizado como npm o WordPress.org.

Por ahora solo hay complementos gratuitos, y la política es manejar los complementos pagados con el mismo modelo descentralizado mientras observamos el progreso del lado del protocolo AT.

El sistema de traducción podría implementarse como un complemento.

En ゆるろぐ, estamos ejecutando un sistema que crea borradores en inglés a partir de artículos en japonés mediante LLM ((Cómo crear un blog multilingüe con EmDash)). Actualmente, el flujo es: un script de Node lee el token de API de EmDash y las claves de la API de traducción desde .env, obtiene el artículo en japonés, lo traduce y crea un borrador en inglés con translationOf.

Si lo aplicamos a la especificación de complementos de la versión 1.x, se convierte casi directamente en un complemento Standard.

  • Disparador: gancho content:afterPublish. Se ejecuta cuando se publica un artículo en japonés.
  • Lectura y escritura de artículos: content:read y content:write. Los resultados de la traducción se crean como borrador.
  • Llamadas a la API de traducción: network:request y agregar el host de la API a allowedHosts.
  • Clave API: campo secret en settingsSchema. Se ingresa desde la pantalla de administración y se guarda encriptado. Ya no necesitas .env ni emisión de tokens API.
  • Estado de traducción: almacenar en storage "qué revisión del artículo original se tradujo", y mostrar en la pantalla de edición de artículos a través de editorPanels mensajes como "versión en inglés disponible / el artículo original fue actualizado, requiere retraducción".

Los scripts que solían ejecutarse manualmente ahora se ejecutan simplemente con que el administrador apruebe la Capability. Además, el runtime garantiza que el código no pueda acceder a hosts que no sean la API de traducción. Lo mejor de este enfoque es que elimina estructuralmente la preocupación sobre "dónde colocar el código que maneja la clave API".

Hay un punto importante en el diseño. Cada vez que el plugin en la sandbox solicita algo al host (leer configuración, obtener un artículo, escribir en storage), se cuenta como una subrequests, y el límite predeterminado es 10. Para traducir un único artículo probablemente sea suficiente, pero para procesos como "traducir múltiples artículos sin traducir", si ejecutas ctx.content.get() en un bucle, rápidamente alcanzarás el límite. Necesitarás un diseño que procese datos poco a poco usando hooks cron.

Además, las interfaces de usuario en el sitio como el cambio de idioma no se pueden emitir desde la sandbox, así que tendrás que escribirlo en el lado del tema.

Conclusión

  • Los plugins de EmDash 1.0 están diseñados con sandbox como base. Declara Capabilities, el administrador las aprueba, y el runtime las refuerza.
  • El mismo código funciona en ambos modos: Trusted y Sandboxed. El desarrollo es en Trusted, la producción es en sandbox, ese es el enfoque básico.
  • Si necesitas bloques personalizados del editor o componentes Astro, usa Native (solo Trusted).
  • El descriptor (tiempo de compilación) y la definition (tiempo de ejecución) deben estar siempre en archivos separados
  • El nombre de Capability cambió en 1.x al formato content:read. El nombre anterior funciona como alias pero está deprecado
  • El registro es descentralizado sobre AT Protocol. El catálogo se puede ocultar pero no se puede falsificar
  • En la zona de pruebas, «una llamada al host = una subconsulta» con un límite de 10. Diseña considerando el número de llamadas

Con WordPress, cada vez que instalas un complemento aceptas que «este complemento tiene todos los permisos del sitio».
Si hay un complemento con vulnerabilidades, todo el sitio queda expuesto al riesgo.

EmDash 1.0 muestra los permisos que puede usar cada complemento en modo sandbox en el momento de la instalación, y el tiempo de ejecución solo permite lo que se aprobó.
Para quienes buscan migrar desde WordPress, la seguridad de los complementos es una de las razones para elegir EmDash.

Aún hay pocos complementos y estamos lejos de la situación de WordPress donde «si buscas encuentras algo», pero espero que aumenten desde aquí.
Ya tenemos experiencia operativa en nuestro propio sitio, así que planeamos agregarlo a nuestros servicios.

Autor de este artículo

Desde que saltó del mundo del DTP a la web, ha dominado markups, frontend, dirección y accesibilidad, convirtiéndose en el "sabio técnico" de la empresa. Ha sido un pilar multifacético desde los inicios de Liberogic y es ahora una referencia indispensable dentro de la organización. Últimamente está explorando eficiencias basadas en prompts, preguntándose «¿podríamos delegar más trabajo de accesibilidad a la IA?». Tanto su tecnología como su pensamiento siguen evolucionando.

Ayumi Futamata

Especialista en web accesibilidad certificado por IAAP (WAS) / Ingeniero de markups / Ingeniero frontend / Director web

Ver artículos de este staff

Destacamos por nuestro equipo confiable y nuestra rápida capacidad de respuesta

En Liberogic, nuestro personal experimentado impulsa activamente los proyectos, lo que nos ha ganado una alta evaluación de nuestros clientes.
Nos aseguramos de asignar adecuadamente gerentes de proyecto y directores, manteniendo una ejecución fluida de todo el proyecto. Evitamos aumentos de costos innecesarios mediante asignación completa, distribuyendo recursos estratégicamente según las necesidades, y somos reconocidos por la rapidez en la comprensión de tareas, elaboración de presupuestos y entrega de cotizaciones.

* Tome en cuenta que nuestra empresa no participa activamente en labores de residencia tipo SES.

Puede utilizar prácticamente todas las herramientas principales de gestión de proyectos y chat como Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, Webex, entre otras.

Consulte con nosotros sobre sus dudas relacionadas con la web.

Casos de Estudio