EmDash: an Astro CMS that rebuilds WordPress around sandboxed, capability-scoped plugins
EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress.
At a glance
- What is it?
- EmDash is an MIT TypeScript CMS delivered as an Astro integration, running on Cloudflare D1, R2 and Workers or on a Node.js server with SQLite. Its strongest idea is plugin isolation with declared capabilities; its weak spots are a plugin layer still on 0.x and a Docker path that only builds the blog template.
- Who is it for?
- EmDash fits developers who already build with Astro, want a WordPress-style admin and editorial roles, and care about limiting what third-party plugins can touch; Cloudflare users get the most direct path.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- emdash-cms/emdash14,416 ★ · +2,965
| Repository | 2026-07-28 | 2026-11-14 |
|---|---|---|
| emdash-cms/emdash | 11.5k | 14.4k |
2,965 Stars in the last 109 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Plugins get only the capabilities their manifest declares
The design decision that separates EmDash from WordPress is plugin isolation. A WordPress plugin has full access to the database, filesystem and user data, so one vulnerable plugin can compromise the whole site. EmDash runs each plugin in its own Worker sandbox through Cloudflare's Dynamic Worker Loaders, and each plugin declares a capability manifest. A plugin that asks for content:read and email:send gets those two abilities and nothing more:
export default () =>
definePlugin({
id: "notify-on-publish",
capabilities: ["content:read", "email:send"],
hooks: {
"content:afterSave": async (event, ctx) => {
if (event.content.status !== "published") return;
await ctx.email.send({
to: "[email protected]",
subject: `New post: ${event.content.title}`,
});
},
},
});The definePlugin() API covers lifecycle hooks such as content:afterSave, KV storage, settings, admin pages, dashboard widgets, custom block types and API routes. For a site owner, the capability list is the thing to read before installing a plugin, since it states the full extent of what that code can reach.
On Node.js the sandbox is a workerd child process
Isolation is not exclusive to Cloudflare. On a Node.js server, plugins run in a workerd child process, which is also sandboxed. That keeps the security model consistent between the two hosting options, but the mechanics differ: on Cloudflare a plugin is a Worker isolate, on Node it lives in a separate process that your server spawns. If you self-host, that child process is part of what you operate and monitor.
The rest of the platform follows the same pattern of a Cloudflare default with portable alternatives. The database is D1 on Cloudflare, or SQLite, Turso/libSQL or PostgreSQL elsewhere, through Kysely. Storage is R2, or AWS S3, any S3-compatible service, or the local filesystem. Sessions use KV on Cloudflare, or Redis or file-based storage. The project states plainly that it runs best on Cloudflare without being locked to it, so expect the Cloudflare path to be the one with the most direct tooling, including a one-click deploy of the blog-cloudflare template.
Content types live in the database, so TypeScript types are generated after the fact
EmDash is added to an existing Astro project as an integration, and that one line brings an admin panel, REST API, authentication, media library and plugin system:
// astro.config.mjs
import emdash from "emdash/astro";
import { d1 } from "emdash/db";
export default defineConfig({
integrations: [emdash({ database: d1() })],
});Content types are defined in the database rather than in code. Non-developers create and change collections in the admin UI's visual schema builder, and each collection becomes a real SQL table with typed columns. Developers pull types from the live schema with:
npx emdash typesThe consequence is a workflow dependency: when an editor adds a field in the admin, your code does not know about it until someone reruns that command. Pages read content through Astro's Live Collections with getEmDashCollection("posts"), so new entries appear without a rebuild and without a separate API layer.
Portable Text storage means WordPress HTML has to be converted, and plugins have to be ported
WordPress stores rich text as HTML with metadata hidden in comments, which ties content to its DOM. EmDash stores rich text as Portable Text, a structured JSON format, edited through TipTap. The same entry can then render as a web page, a mobile app view, an email or an API response without parsing HTML. The workspace includes a @emdash-cms/gutenberg-to-portable-text package, whose name points at converting Gutenberg block content into that format.
Migration covers content well: posts, pages, media and taxonomies import from WXR exports, the WordPress REST API or WordPress.com, through a WordPress import wizard in the admin. Plugins and themes are a different matter. There is no PHP, so a WordPress plugin cannot be installed as is; agent skills are offered to help port plugins and themes. For a site that leans on a handful of specific WordPress plugins, the migration cost sits in rewriting those, not in moving posts.
The core is semver-stable, first-party plugins are still 0.x
EmDash calls itself stable and ready for production, and that claim has a defined boundary. The core packages, emdash, create-emdash and the @emdash-cms admin, auth, blocks and Cloudflare packages, follow semantic versioning: breaking changes only arrive in major releases and are called out in the changelog. First-party plugins and plugin tooling are still on 0.x and may change between minor releases.
If you write your own plugins or depend on first-party ones, an ordinary minor upgrade can break them. Pin those versions and read the changelog before each update. Releases ship as a coordinated set: [email protected], [email protected] and @emdash-cms/[email protected] were all tagged on 2026-10-29, and the last push to main was on 2026-11-02. The repository also carries ROADMAP.md, GOVERNANCE.md, an rfcs/ folder and a proposals/ folder for anyone who wants to follow where the plugin APIs are headed.
The Docker image only packages the blog template
The quickest start is the scaffolder:
npm create emdash@latestIt offers three starter templates. Blog has categories and tags, full-text search, comment-ready posts, RSS and dark or light mode. Marketing is a photo-led landing page built from blocks such as a hero, logo wall, pricing and FAQ. Portfolio has a project grid, tag filtering, case study pages and RSS.
The repository's Dockerfile and compose.yaml take a narrower route. The image builds from node:22-slim with pnpm 10.28.0, sets NODE_OPTIONS=--max-old-space-size=4096 because package compilation can exceed Node's default heap, and then deploys only @emdash-cms/template-blog, rewriting its database path to ./data/data.db. Compose exposes port 4321 and keeps two named volumes, emdash-data and emdash-uploads. If you want the marketing or portfolio template in a container, you have to change the build yourself, and building the image needs a machine with enough memory for a 4 GB Node heap.
Passkey-first auth, editorial roles and an MCP server for agents
Authentication starts from passkeys over WebAuthn, with OAuth and magic links as fallbacks. Access control uses four roles taken from the WordPress playbook: Administrator, Editor, Author and Contributor. Editorial features include revisions, drafts, scheduled publishing, inline visual editing and full-text search backed by SQLite FTS5. The media library handles drag-and-drop uploads through signed URLs, and the admin also manages navigation menus, taxonomies and widgets.
EmDash is also built to be operated by coding agents. It publishes agent skills that teach assistants its APIs, ships a CLI that lets agents manage content and schema programmatically, and includes a built-in MCP server so tools such as Claude and ChatGPT can act on a site directly. The workspace description calls the project an agent-portable reimplementation of WordPress on Astro. Giving an AI tool MCP access to a live site is a permission decision worth making deliberately, since the point of the server is to let that tool interact with your site directly.
Editorial conclusion
EmDash fits developers who already build with Astro, want a WordPress-style admin and editorial roles, and care about limiting what third-party plugins can touch; Cloudflare users get the most direct path. It fits poorly if your site depends on specific WordPress plugins, because those have to be ported rather than installed, or if you plan to build on plugin APIs that are still 0.x. Before migrating, run a WXR export through the import wizard on a test site, and check the changelog for whether the plugin packages you need have reached 1.0.
Frequently asked questions
How do you install EmDash?
Run npm create emdash@latest to scaffold a site from a template, or use the deploy button to put the blog-cloudflare template on your Cloudflare account. An existing Astro project adds EmDash as an integration in astro.config.mjs.
Can EmDash run without Cloudflare?
Yes. EmDash runs on any Node.js server with SQLite, and also supports Turso/libSQL, PostgreSQL, S3-compatible storage and Redis sessions. Plugins on Node.js run sandboxed in a workerd child process.
Can EmDash use existing WordPress plugins?
No. EmDash has no PHP, so WordPress plugins and themes must be ported; agent skills are provided to help. Posts, pages, media and taxonomies import from WXR exports, the WordPress REST API or WordPress.com.
Is EmDash ready for production?
The project states that EmDash is stable and ready for production, with core packages on semantic versioning. First-party plugins and plugin tooling are still on 0.x and may change between minor releases.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/emdash-cms-emdash)Star history chart for your README
[](https://hysenlabs.com/projects/emdash-cms-emdash)Was this useful?