← Volver a todos los artículos

Cloudflare EmDash CMS: sucesor open source de WordPress

Actualizado: 2 de noviembre de 2026
Cloudflare EmDash CMS: sucesor open source de WordPress

Cloudflare lanzó EmDash como developer preview, presentándolo como “the spiritual successor to WordPress”. La cobertura fue cortés pero clara: “not yet suitable for migrating production WordPress sites.”

Lo teníamos corriendo el blog de producción de Prepa IN a las 8:17 de la tarde del día siguiente.

Este post es por qué hicimos esa apuesta, cómo funcionan EmDash getting started y EmDash admin en la práctica, cómo se ve la arquitectura desde adentro, y la parte de la que más orgullo tengo: el bug de escala que encontramos a los 124 posts y arreglamos en EmDash core, mergeado upstream nueve días después de tumbar nuestro blog.

Por qué volvimos a un CMS

Cuando migramos Prepa IN fuera de WordPress en un fin de semana, el contenido quedó como markdown en git. Para un developer es un sueño. Para todos los demás es una puerta cerrada: cada typo era un pull request.

El requerimiento era estrecho: algo que pueda editar alguien que no es developer, que no meta un host PHP en un stack de Cloudflare Workers, y que no deje seis años de contenido detrás de la API de alguien más. Y un requerimiento que lo decidió: el CMS tenía que ser AI-first. Para entonces la mayor parte de nuestras operaciones de contenido las corrían agentes, y cada CMS que evaluamos trataba eso como un problema de integración, una API key aquí, un webhook allá. Quería un CMS donde casi todo lo que harías en el admin UI lo pudiera hacer un agente de forma nativa, con permisos reales. Cuando buscamos, esa categoría casi no existía. Entonces Cloudflare lanzó uno.

EmDash getting started

El EmDash getting started oficial está en docs.emdashcms.com. Dos caminos llegan al mismo sitio: un proyecto generado, o la integración de Astro sobre uno existente.

Generar un sitio. Node.js 22.23.4 o posterior. Según la documentación:

npm create emdash@latest

Elige destino de deploy y plantilla (por ejemplo Node starter, o blog en Cloudflare con --template cloudflare:blog). El scaffolder escribe un .env ignorado por git con EMDASH_ENCRYPTION_KEY. Arranca el servidor de desarrollo, abre /_emdash/admin y termina el asistente: título del sitio, email/nombre de admin y registro de passkey. El contenido de muestra y el content model salen del seed de la plantilla si dejas esa opción seleccionada.

Agregar EmDash a una app Astro existente. El proyecto necesita output: "server", un adapter, React (el EmDash admin es una app React), un adapter de base de datos y almacenamiento de media. El camino de Cloudflare es el que corremos: D1 para SQL y R2 para media. Las Live Collections de Astro se conectan con emdashLoader() para que las páginas lean contenido publicado en request time.

Nuestra instalación en producción, en astro.config.mjs, literal:

emdash({
  database: d1({ binding: 'DB' }),
  storage: r2({ binding: 'MEDIA' }),
  mcp: true,
}),

Esas tres líneas te dan EmDash admin en /_emdash/admin, una REST API, autenticación, media library, sistema de plugins y un servidor MCP, todo dentro de tu propio Worker. Después del deploy, completa el mismo setup de admin en la URL en vivo. Quienes cambian el content model en admin pueden correr npx emdash types para generar TypeScript desde el schema en vivo.

Cloudflare dice que construyó EmDash en unos dos meses con AI agentic. Nosotros migramos en una tarde con AI agentic. El resto de este post es a qué nos comprometió esa tarde.

EmDash admin

El EmDash admin es la interfaz de edición que sirve tu app Astro en /_emdash/admin/. Usa la misma base de datos, media, content model y plugins que el sitio público. La primera visita en un sitio nuevo abre el asistente de setup; después, el rol con sesión decide qué ves.

Según la documentación del admin panel, las áreas principales son:

  • Dashboard para widgets del sitio y de plugins
  • Colecciones como Posts: listar, crear, editar, publicar, trash
  • Media para subir, buscar, reemplazar y borrar
  • Taxonomías y bylines para organización y autores acreditados
  • Menús y widget areas cuando el theme los consulta
  • Content types para crear colecciones y campos
  • Plugins y Settings (sitio, auth, backups, email, search y servicios relacionados)

Los roles son Subscriber, Contributor, Author, Editor y Admin. Los permisos se acumulan hacia arriba; la ownership sigue importando en contenido y media. Los Editors pueden gestionar contenido de otros autores y varias superficies editoriales; los Admins pueden cambiar el content model, usuarios, plugins, redirects, backups y settings, y borrar de forma permanente lo que está en trash. Ocultar un ítem de navegación no es el límite de seguridad; el servidor valida cada acción.

En colecciones con borradores, Save y Publish son estados separados. Save guarda un draft; Publish (o Publish changes) hace visible esa versión a las queries públicas. El scheduling registra cuándo un draft debe publicarse. El edit locking, cuando está activo, rechaza sobreescrituras concurrentes.

Ese es el EmDash admin que usa marketing en Prepa IN: abren /_emdash/admin, editan Portable Text en el block editor, adjuntan media y publican. Ya nadie abre un pull request para corregir un typo. Los agentes llegan a las mismas capacidades por el servidor MCP con OAuth acotado (content:*, media:*, schema:* y admin), por eso el admin UI y la superficie de agentes son pares, no una API key pegada al costado.

Qué es EmDash

EmDash es un CMS full-stack en TypeScript, open source con licencia MIT, construido por Cloudflare: Astro-first, corre en Workers con D1 para SQL y R2 para media, y no contiene código de WordPress.

La arquitectura, revisada

Ya viví dentro de este codebase como consumidor y como contributor, así que esta es la review que ojalá hubiera tenido el día del lanzamiento.

Es una dependencia, no una plataforma. EmDash es una integración de Astro en tu package.json, corriendo dentro de tu deploy, en tu cuenta de Cloudflare. No hay una nube de EmDash entre tú y tu contenido. Suena a detalle. Es lo que hizo posible la historia de contribución de abajo.

El schema vive en la base de datos, no en el código. Las colecciones creadas en EmDash admin se vuelven tablas SQL reales con columnas tipadas; los developers corren npx emdash types para generar TypeScript desde el schema en vivo y consultan con Live Collections de Astro sin rebuilds. Es la inversión correcta: quienes cambian content models rara vez son quienes despliegan código.

El contenido es Portable Text, no HTML serializado, así que el mismo contenido se renderiza como página, feed, email o respuesta de API sin parsear HTML.

Los plugins corren en sandbox con capacidades declaradas. La tesis de Cloudflare, citando datos de Patchstack, es que el 96% de las vulnerabilidades de WordPress vienen de plugins con acceso total. Los plugins de EmDash corren en sandboxes aislados de Worker, cada uno con un manifest de capacidades: un plugin que declara read:content y email:send puede hacer exactamente eso y nada más. Un caveat: el sandbox requiere un plan de Workers de pago.

Y es portable por diseño. El SQL pasa por Kysely, el storage habla la API de S3; rinde mejor en D1 y R2 pero también en SQLite, Turso, Postgres o archivos locales. El servidor MCP, las agent skills y la CLI son pares del EmDash admin UI.

Lo que marcaría a un CTO: el modelo schema-in-database significa que tu content model no está en version control (trae tu propia disciplina de seed y migraciones), y un ritmo de releases v0.x en la dependencia que renderiza todo el sitio es un costo operativo recurrente. Ambos precios los pagamos a sabiendas.

El bug que encontramos, y el fix en core

A los dos días, el blog dejó de cargar.

Cloudflare D1 limita a 100 los parámetros ligados por query. EmDash hidrataba bylines de toda una colección en una sola cláusula IN (?, ?, ...), una variable por post. Nuestro corpus cruzó 124 posts, y cada página que listaba contenido empezó a fallar con:

D1_ERROR: too many SQL variables at offset 369: SQLITE_ERROR

Con WordPress, aquí abres un ticket de soporte contra un plugin y refrescas un changelog. Aquí el CMS era TypeScript en nuestro propio node_modules. Así que: leer el source, escribir el fix, cargarlo en local con patch-package, subir a producción el mismo día y mandar el fix upstream.

El fix es del tipo aburrido que sobrevive review: un helper compartido chunks(), un SQL_BATCH_SIZE de 50, IDs deduplicados para que un registro que cruce el borde de un chunk no se acredite dos veces. Mientras lo escribía encontré el mismo patrón de IN sin tope en el repositorio de metadata SEO, que falla todavía antes. Dos pull requests, #223 para bylines y #422 para SEO, con diez tests nuevos entre ambos. Se mergearon en emdash-cms/emdash dentro de la semana; dos días después borramos nuestros parches locales, porque el siguiente release upstream ya traía todo lo que estábamos cargando. Nueve días desde “el blog está caído” hasta “el fix está en el producto,” sin que producción estuviera abajo más de la primera hora.

Ese es el argumento por el que existe este post. Un bug en un plugin de WordPress es un ticket en la cola de alguien más. Un bug en un CMS que es una dependencia legible es un branch. Hay un prerrequisito: alguien de tu lado tiene que leer TypeScript. Pero esa es exactamente la capacidad que un founder técnico debería estar contratando de todos modos.

Lo que nos compró mcp: true

Esa sola bandera convierte el CMS en una superficie que los agentes pueden operar. No una API key pegada al costado: un resource server OAuth 2.0 completo, con authorization code, refresh token y device code flows, PKCE y permisos acotados:

content:read   content:write
media:read     media:write
schema:read    schema:write
admin

Sobre esa superficie construimos el pipeline editorial autónomo que conté en cómo una flota de Claude managed agents multiplicó por 20 el SEO y la AI visibility de Prepa IN. Los estándares editoriales se volvieron ejecutables: “cada post necesita hero image, byline, SEO title y description” dejó de ser un documento que nadie lee y se convirtió en un reviewer que falla cerrado. Los editores siguen trabajando en EmDash admin; los agentes usan el mismo modelo de permisos por MCP.

El cálculo de riesgo

Tomamos cada release conforme llegaba, más o menos un minor al mes en la dependencia que renderiza todo el sitio. Ese ritmo no es gratis, pero cada upgrade ha llegado con fixes que queríamos, dos veces incluyendo los nuestros.

Esta fue la apuesta correcta porque el blog es recuperable. El contenido vive en D1 y R2 en nuestra cuenta, con un seed en el repo, y las abstracciones son portables por diseño. Si EmDash nos hubiera fallado, la salida era un script, no una negociación de rehenes. Apostar un checkout o nuestra plataforma de estudiantes a un preview de un día habría sido imprudente. Apostar un blog sí fue un riesgo calculado con piso conocido.

Lo que le diría a un founder o CTO que evalúa algo parecido:

Precia el piso, no la etiqueta. “Developer preview” describe la madurez del software, no tu riesgo. Tu riesgo es la ruta de recuperación. La nuestra era corta y probada; eso hizo sensata la adopción día uno.

Elige dependencias que alguien de tu equipo pueda leer. Cada ventaja de esta historia, desde el primer patch hasta los PRs mergeados, vino de que el CMS era TypeScript legible en nuestro propio tree. Open source que no puedes leer es solo un vendor más lento.

Evalúa un CMS por lo que un agente puede hacer en él sin supervisión. OAuth acotado para agentes, una superficie MCP, un schema que un agente pueda tocar con seguridad: esos checks son los que pusieron a EmDash por delante de todo lo demás que evaluamos.

El arco completo, en una línea: WordPress, luego markdown en git, luego EmDash en D1. Salimos de un CMS, pasamos tres meses sin uno y volvimos a otro tipo: uno que nuestros agentes pueden operar, y uno al que podemos mandar un pull request.

Si estás evaluando EmDash, pasando por EmDash getting started, comparando EmDash admin con el stack que tienes, o armando el equipo que puede hacer apuestas así con seguridad, me encantaría comparar notas. Soy Angel Baez, Applied AI Engineer y co-founder de Prepa IN y Recupera. Me encuentras en LinkedIn o X, y Prepa IN en prepain.mx.

También disponible en inglés.

Preguntas frecuentes

¿Qué es EmDash CMS?

EmDash es el CMS open source de Cloudflare, con licencia MIT y escrito en TypeScript, presentado como el sucesor espiritual de WordPress. Se instala como integración de Astro y corre en Cloudflare Workers con D1 para SQL y R2 para media, con abstracciones portables que también funcionan con SQLite, Turso, Postgres y S3. El contenido se guarda como Portable Text, los plugins corren en isolates de Worker con capacidades declaradas, y trae un servidor MCP integrado para que agentes de IA operen el CMS directamente.

¿Está EmDash listo para producción?

La cobertura del lanzamiento dijo que el developer preview era "not yet suitable for migrating production WordPress sites." Aun así corrimos el blog de producción de Prepa IN al día siguiente como un riesgo calculado con piso conocido: el contenido vive en D1 y R2 en nuestra cuenta de Cloudflare, con un seed en el repo, y las abstracciones de EmDash son portables por diseño, así que el peor caso era recuperable. No habríamos hecho la misma apuesta con un checkout o con la plataforma de estudiantes. El post también señala el costo operativo recurrente de un ritmo de releases v0.x, y un bug de escala a los 124 posts que arreglamos upstream.

¿Qué es EmDash MCP?

EmDash trae un servidor MCP integrado. Con mcp: true en la integración de Astro, el CMS se vuelve una superficie que los agentes pueden operar: un resource server OAuth 2.0 completo, con authorization code, refresh token y device code flows, PKCE y permisos acotados (content:read, content:write, media:read, media:write, schema:read, schema:write y admin). El servidor MCP, las agent skills y la CLI son pares del EmDash admin UI, así que casi todo lo que harías en el admin un agente puede hacerlo con permisos con alcance y revocables.

¿Se puede self-hostear EmDash?

Sí. EmDash es una integración de Astro en tu package.json que corre dentro de tu propio deploy, en tu cuenta de Cloudflare; no hay una nube de EmDash entre tú y tu contenido. En Cloudflare usa Workers con D1 para SQL y R2 para media, y es portable por diseño a SQLite, Turso, Postgres y storage compatible con S3 o archivos locales.

¿Cómo empiezo con EmDash (getting started)?

Puedes crear un sitio con npm create emdash@latest (plantillas de blog en Cloudflare o starter en Node), o agregar la integración emdash a un proyecto Astro con output server, base de datos y almacenamiento de media. Arranca el servidor de desarrollo, abre /_emdash/admin y completa el asistente de setup (título del sitio, cuenta de admin, passkey). En Cloudflare configura bindings de D1 y R2, despliega el Worker y termina el mismo setup en la URL en vivo. Docs oficiales: docs.emdashcms.com.

¿Qué es el EmDash admin?

El EmDash admin es la UI de edición en React servida en /_emdash/admin dentro de tu app Astro. Tras el setup, el rol con sesión (Subscriber hasta Admin) controla qué áreas ves: dashboard, colecciones como Posts, media, taxonomías y bylines, menús, content types, plugins y settings. Save y Publish son estados separados en colecciones con borradores. En Prepa IN, marketing edita ahí; los agentes usan la superficie MCP con OAuth acotado, incluido el scope admin.

¿Qué significa en la práctica un CMS AI-first?

El CMS trata a los agentes como usuarios de primera clase. EmDash expone casi todo lo que puede hacer un admin a través de un servidor MCP con OAuth acotado, más agent skills y una CLI. Editar contenido o cambiar el schema es algo que un agente puede hacer con permisos con alcance y revocables.

¿Fue imprudente adoptar en producción un developer preview de un día?

Fue un riesgo calculado con piso conocido. El contenido vive en D1 y R2 en nuestra cuenta de Cloudflare, con un seed en el repo, y las abstracciones de EmDash son portables por diseño, así que el peor caso era recuperable. No habríamos hecho la misma apuesta con un checkout o con la plataforma de estudiantes.

¿Cuál fue el bug que encontraron y cuyo fix contribuyeron?

Cloudflare D1 limita a 100 los parámetros SQL ligados por query, y EmDash hidrataba bylines y metadata SEO de toda una colección en cláusulas IN (...) únicas. Al cruzar 124 posts, el blog dejó de cargar con D1_ERROR: too many SQL variables. Lo parchamos en local y enviamos dos fixes upstream (PRs #223 y #422) que particionan esas queries en lotes de 50, con tests. Ambos se mergearon en emdash-cms/emdash en días.