CLI tool
emdash-cms/emdash avatar
emdash-cms/emdash

EmDash CMS: a full-stack TypeScript CMS on Astro and Cloudflare

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress.

12,551 stars1,172 forksTypeScriptMIT

At a glance

What is it?
EmDash rebuilds the WordPress model (admin panel, plugins, content types) on Astro, D1 and Worker isolates. It is in beta preview, and its sandboxed plugin system depends on a paid Cloudflare feature.
Who is it for?
Adopt EmDash if your site already lives on Cloudflare Workers and you want content types, an admin panel and a plugin model without PHP. Do not adopt it if you need a stable, long-supported CMS today: the README calls it a beta preview, and the sandboxed plugin path depends on Dynamic Workers, which the README says are only available on paid Cloudflare accounts.
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 last received commits 3 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 26, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What EmDash is trying to replace, and for whom

EmDash is a full-stack TypeScript CMS built on Astro and Cloudflare. The README frames it as the spiritual successor to WordPress: it keeps the parts that made WordPress dominant (extensibility, an admin UX, a plugin ecosystem) and rebuilds them on serverless, type-safe foundations. The repository's own package.json describes the workspace as an "agent-portable reimplementation of WordPress on Astro", which is a more precise statement of intent than the marketing line.

The audience is narrow and specific. You need to be comfortable with Astro, with TypeScript, and with deploying to Cloudflare Workers or running a Node.js server with SQLite. If your team writes PHP today and has no Astro experience, EmDash is a rewrite, not a migration. The WordPress import wizard moves posts, pages, media and taxonomies, and the README says agent skills help port plugins and themes, but a ported theme is still a ported theme.

The problem it actually solves is the plugin trust boundary. The README cites the claim that 96% of WordPress security vulnerabilities come from plugins, and WordPress plugins do run with full access to the database, filesystem and user data. EmDash's answer is a capability manifest plus an isolated Worker sandbox, which is a structural change rather than a patching strategy.

Astro integration, SQL-backed collections, and the plugin sandbox

EmDash is an Astro integration, not a standalone application you reverse-proxy in front of. The README's example config shows `emdash({ database: d1() })` inside `integrations`, and the comment says that adding it gives you the admin panel, REST API, authentication, media library and plugin system.

Content types live in the database, not in code. Non-developers create and modify collections through the admin UI, and each collection becomes a real SQL table with typed columns. Developers then generate TypeScript types from the live schema. That inversion is the most interesting design decision here: schema drift between code and database is impossible because the database is the source of truth, but it also means your content model is not reviewable in a pull request unless you export it.

Plugins are the second mechanism. The README's example declares `capabilities: ["read:content", "email:send"]` and a `content:afterSave` hook, with the note that a plugin requesting those two capabilities can do exactly that and nothing else. On Cloudflare, plugins run in Worker isolates via Dynamic Worker Loaders. The portability table in the README lists an in-process "safe mode" as the non-Cloudflare fallback, which is a meaningfully weaker guarantee: in-process plugins share the host's privileges.

Underneath, the README describes portable abstractions: Kysely for SQL, the S3 API for storage. The supported matrix is D1, SQLite, Turso/libSQL and PostgreSQL for data, and R2, AWS S3, any S3-compatible service or the local filesystem for media. Sessions can use KV, Redis or files. That spread is what makes the Docker path in the repository plausible rather than decorative.

Installing EmDash and creating your first collection

The README gives one scaffold command. It runs the `create-emdash` package, which is published in lockstep with the main package (both are at 0.35.0 in the release list).

bash
npm create emdash@latest

After scaffolding you get one of the three starter templates the README documents: Blog (categories, tags, full-text search, RSS, dark/light mode), Marketing (hero, feature grid, pricing cards, FAQ and contact form) or Portfolio (project grid, tag filtering, case study pages, RSS).

If you want to wire EmDash into an existing Astro project instead, the README shows the integration and a database adapter imported from `emdash/db`:

typescript
// astro.config.mjs
import emdash from "emdash/astro";
import { d1 } from "emdash/db";

export default defineConfig({
	integrations: [emdash({ database: d1() })],
});

Once a collection exists in the admin UI, the CLI generates TypeScript types from the live schema:

bash
npx emdash types

Reading that content in a page uses Astro's Live Collections, which the README says means no rebuilds and no separate API call:

astro
---
import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");
---

{posts.map((post) => <article>{post.data.title}</article>)}

The repository also ships a Dockerfile and a compose file. The compose service builds from the local Dockerfile, maps port 4321 and mounts two named volumes, `emdash-data` and `emdash-uploads`. The Dockerfile's build stage rewrites `templates/blog/astro.config.mjs` so the SQLite file resolves to `file:./data/data.db`, which matches the mounted data volume.

bash
docker compose up

The paid-account dependency and the safe-mode fallback

The README carries an explicit warning: EmDash depends on Dynamic Workers to run secure sandboxed plugins, and Dynamic Workers are currently only available on paid accounts. The suggested workarounds are upgrading (the README says starting at $5/mo) or commenting out the `worker_loaders` block of `wrangler.jsonc` to disable plugins.

That is a real constraint, not a footnote. The plugin sandbox is the headline argument for choosing EmDash over WordPress, and on a free Cloudflare account you either pay or you turn the feature off. The portability table's "in-process (safe mode)" column is the other option, and it gives up the isolation that the rest of the pitch rests on. Anyone evaluating EmDash for a small site should price the Workers plan before they price anything else.

Beta status is the second limitation. The README states plainly that EmDash is in beta preview, and the release list shows a 0.x version line. The README does not document a rollback procedure, a migration path between EmDash versions, or a stability guarantee for the plugin API, so a plugin written against 0.35.0 has no stated contract protecting it.

The third limitation is operational. Cloudflare's D1, R2 and KV are managed services; running the Node.js path with SQLite means you own backups, disk and process supervision yourself. The README mentions that EmDash runs on Cloudflare or any Node.js server with SQLite, but it does not describe a backup tool, a restore command, or a replication story for the self-hosted case.

EmDash versus WordPress, and versus a headless CMS

Against WordPress, the difference is the storage format and the plugin boundary, not the feature list. WordPress serializes rich text as HTML with metadata embedded in comments, which ties content to its DOM representation. EmDash stores rich text as Portable Text, a structured JSON format, so the same entry can render as a web page, a mobile view, an email or an API response without HTML parsing. That matters if you plan to reuse content outside the website; it matters much less if you only ever render one theme.

The plugin difference is sharper. A WordPress plugin can read the database and the filesystem. An EmDash plugin declares capabilities such as `read:content` and `email:send` and is confined to them, provided it runs in a Worker isolate. WordPress has the larger ecosystem by an enormous margin, and the README's migration wizard imports content rather than installing plugins, so you should assume your WordPress plugin stack does not come with you.

Against a headless CMS (Contentful, Sanity, Strapi and similar), the difference is where the rendering lives. A headless CMS gives you an API and leaves the front end to you. EmDash is an Astro integration: the admin panel, the API and the site rendering ship together, and `getEmDashCollection` reads content directly in an Astro page with no rebuild step. If you want to render the same content in a native mobile app and a website from one backend, a headless CMS is the more conventional fit. If you want one deployable Astro site with an admin panel bolted on, EmDash is the shorter path.

Licence, maintenance and what upgrades cost

EmDash is MIT licensed. That is permissive: you can use it commercially, modify it and redistribute it, provided you keep the copyright notice and licence text. It says nothing about the services EmDash depends on. Your Cloudflare bill, your D1 and R2 usage, and the paid Workers plan needed for Dynamic Workers are separate commercial relationships that the MIT licence does not touch. The same applies to any third-party plugin or theme you install.

The repository is not archived, and the last push was on 2026-08-24, which is recent enough that the project is being worked on. The workspace layout supports that reading: there are `packages/`, `apps/`, `templates/`, `e2e/`, `acceptance/`, `i18n/`, `infra/`, `skills/` and a `.changeset/` directory, plus a `renovate.json` for dependency updates. The release list shows three packages versioned together at 0.35.0, including `create-emdash` and `@emdash-cms/x402`, so the scaffold and the core move in step.

Upgrade cost is the open question. The README does not document a schema migration process, a changelog policy for breaking changes, or a rollback path, and the project is pre-1.0. The practical consequence is that you should treat an EmDash upgrade as a code change that needs testing, not a routine dependency bump. The repository does include Playwright end-to-end suites and a separate table-mode config, which gives you a template for testing your own site after an upgrade, but nothing in the README promises that a template you forked will still build after the next minor release.

Editorial conclusion

Adopt EmDash if your site already lives on Cloudflare Workers and you want content types, an admin panel and a plugin model without PHP. Do not adopt it if you need a stable, long-supported CMS today: the README calls it a beta preview, and the sandboxed plugin path depends on Dynamic Workers, which the README says are only available on paid Cloudflare accounts. Before committing, run `npx emdash types` against a real collection and confirm the generated types match what your front end expects.

Frequently asked questions

What is EmDash CMS?

It is a full-stack TypeScript CMS built on Astro and Cloudflare, described in its README as the spiritual successor to WordPress. It ships an admin panel, REST API, authentication, media library and a plugin system as an Astro integration.

How do I install EmDash?

The README gives one scaffold command, `npm create emdash@latest`, which produces a Blog, Marketing or Portfolio starter template. You can also add the `emdash/astro` integration and a database adapter such as `d1()` to an existing Astro config.

Does EmDash run on Cloudflare?

Yes. The README says EmDash runs on Cloudflare using D1, R2 and Workers, and that it runs best there while remaining portable to SQLite, Turso/libSQL or PostgreSQL and to S3-compatible storage. The sandboxed plugin path specifically requires Cloudflare's Dynamic Workers.

Why does the README warn about a paid Cloudflare account?

Because EmDash depends on Dynamic Workers to run secure sandboxed plugins, and the README states those are currently only available on paid accounts. The alternatives it gives are upgrading or commenting out the `worker_loaders` block of `wrangler.jsonc` to disable plugins.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/emdash-cms-emdash.svg)](https://hysenlabs.com/projects/emdash-cms-emdash)