Skip to content

EmDash

EmDash is an open-source CMS with an editor that feels familiar if you’ve used WordPress. In the EmDash variant of a template, EmDash runs inside your site: the admin lives at /_emdash/admin, and what you publish there is live on the site at once. There’s no build to wait for and no separate CMS service.

The variant runs on Cloudflare Workers: your content is stored in a D1 database and your photos in an R2 bucket, both in your own Cloudflare account. Locally, npm run dev emulates both, so you don’t need an account to try it.

Files / Sanity variant EmDash variant
Pages Built ahead of time (static) Rendered on request
After publishing Your host rebuilds the site (a minute or two) Live at once
Hosting Anywhere (Vercel, Netlify, Cloudflare…) Cloudflare Workers
Editor Keystatic or Sanity Studio EmDash admin, in your site
Cost Free on most hosts’ free plans Workers Paid ($5 a month) for a live site

You choose the variant when you create the site:

Terminal window
wptom new my-site -t aurora --cms emdash
Terminal window
cd my-site
npm install
npm run dev

Open the site. The first time, it sets EmDash up for you: it creates an admin account (you’re signed in as “Dev Admin”) and loads the demo content, including the photos. Then:

  • the site: the address npm run dev prints
  • the admin: that address plus /_emdash/admin

Edit something, press Publish, reload the site: the change is there.

The admin’s sidebar matches the site:

  • Pages: the home page, landing pages, the pricing page and every other page, legal pages, site settings.
  • Blog: your posts.
  • Content: changelog, docs and the interface texts (buttons, labels, emails).

Single pages (home, pricing, site settings…) have exactly one entry each. Keep its slug as it is (home, pricingPage…): the site finds the page by that slug.

Page sections (hero, steps, pricing, FAQ…) are blocks: add, reorder and remove them in the page. Lists such as pricing plans or FAQ questions are blocks too, one per item. Large sets of texts, such as the interface texts, are grouped (navigation, footer, chat, emails…) so each group is one form.

Posts, docs, changelog entries and legal pages use EmDash’s rich text editor: headings, lists, quotes, links, bold and italic, code and images. Quotes and dashes are typeset for you (” becomes “ and “, – becomes –), and code blocks are highlighted like the rest of the site.

EmDash’s editor has no tables yet. Use a list where you’d use a small table.

Saving keeps a draft; visitors keep seeing the published version until you press Publish. Scheduled entries publish on their own (EmDash checks every minute).

Upload photos in the admin (Media, or straight from an image field). The site serves them resized for each screen in AVIF and WebP, through Cloudflare Images. Its free plan includes 5,000 unique resized versions a month, which is plenty for most sites; past that, versions made before keep working and new ones fail until the next month or a paid plan.

You need a Cloudflare account. For a live site, use the Workers Paid plan ($5 a month): the free plan allows 10 ms of CPU time per request, and EmDash pages take more to render, so visitors would get occasional errors. The free plan is fine for trying the variant out.

  1. Set your domain first. In astro.config.mjs, set site to your production address, e.g. https://www.example.com. EmDash ties sign-in (passkeys) to this address and uses it to resize photos, so set it before the first deploy.

  2. Name your site. In wrangler.jsonc, change name (the Worker), database_name and bucket_name if you like.

  3. Log in and deploy:

    Terminal window
    npx wrangler login
    npm run deploy

    The first deploy creates the D1 database and the R2 bucket for you, and prints the Worker’s address (https://<name>.<you>.workers.dev).

  4. Add your domain. Check that the workers.dev address responds, then add your domain (it must be on Cloudflare in the same account): in the Cloudflare dashboard (Workers & Pages → your Worker → Settings → Domains & Routes), or in wrangler.jsonc:

    "routes": [{ "pattern": "www.example.com", "custom_domain": true }]
  5. Set up EmDash on your domain. Open https://www.example.com/_emdash/admin and finish the setup wizard: you become the administrator and create your passkey. Do this on your real domain: passkeys only work on the address where they were created. Until setup is done, the first person to finish it becomes the administrator, so do it right after deploying.

  6. Feature settings. Add what you use from .env.example (email, analytics, payments, chat) in the dashboard (your Worker → Settings → Variables and Secrets) or with npx wrangler secret put NAME, then deploy again.

Later deploys are just npm run deploy. Your content stays in D1 and R2.

Invite editors from the admin (Users). They sign in with passkeys. Sign-in links by email need an email provider configured in EmDash.

When you add or change fields in src/content/model.ts (see Change the content model), bring EmDash in line:

  1. In the admin, create an API token (Settings → API Tokens) with the schema:write scope.

  2. Run, against your site:

    Terminal window
    EMDASH_URL=https://www.example.com EMDASH_TOKEN=ec_pat_… npm run emdash:schema

    New fields, block types and collections are added and changed ones updated; your content stays. If EmDash can’t change a field automatically (say, a text field that should become a list), the script tells you and you change it in the admin (Content Types).

  3. Deploy the site (npm run deploy).

Locally, use the address npm run dev prints as EMDASH_URL and a token from your local admin.