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 and Sanity vs. EmDash
Section titled “Files and Sanity vs. EmDash”| 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:
wptom new my-site -t aurora --cms emdashTry it locally
Section titled “Try it locally”cd my-sitenpm installnpm run devOpen 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 devprints - the admin: that address plus
/_emdash/admin
Edit something, press Publish, reload the site: the change is there.
How the content is organised
Section titled “How the content is organised”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.
Writing
Section titled “Writing”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.
Drafts and publishing
Section titled “Drafts and publishing”Saving keeps a draft; visitors keep seeing the published version until you press Publish. Scheduled entries publish on their own (EmDash checks every minute).
Photos
Section titled “Photos”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.
Deploy to Cloudflare
Section titled “Deploy to Cloudflare”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.
-
Set your domain first. In
astro.config.mjs, setsiteto 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. -
Name your site. In
wrangler.jsonc, changename(the Worker),database_nameandbucket_nameif you like. -
Log in and deploy:
Terminal window npx wrangler loginnpm run deployThe first deploy creates the D1 database and the R2 bucket for you, and prints the Worker’s address (
https://<name>.<you>.workers.dev). -
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 }] -
Set up EmDash on your domain. Open
https://www.example.com/_emdash/adminand 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. -
Feature settings. Add what you use from
.env.example(email, analytics, payments, chat) in the dashboard (your Worker → Settings → Variables and Secrets) or withnpx wrangler secret put NAME, then deploy again.
Later deploys are just npm run deploy. Your content stays in D1 and R2.
Editors
Section titled “Editors”Invite editors from the admin (Users). They sign in with passkeys. Sign-in links by email need an email provider configured in EmDash.
Change the content model
Section titled “Change the content model”When you add or change fields in src/content/model.ts (see
Change the content model), bring EmDash in line:
-
In the admin, create an API token (Settings → API Tokens) with the
schema:writescope. -
Run, against your site:
Terminal window EMDASH_URL=https://www.example.com EMDASH_TOKEN=ec_pat_… npm run emdash:schemaNew 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).
-
Deploy the site (
npm run deploy).
Locally, use the address npm run dev prints as EMDASH_URL and a token from your local admin.