Skip to content

Add EmDash to an Existing Astro Project

npm create emdash@latest ships a pre-wired project, but EmDash also grafts onto an Astro site you already have. This guide walks through every requirement the starter template normally handles for you — each one produces a confusing failure when it’s missing, so work through the checklist in order.

  • Astro 6 or later — upgrade first if you’re on an older major (npx @astrojs/upgrade)
  • Node.js v22.12.0 or higher (odd-numbered versions are not supported)
  • Server output — EmDash serves content at runtime, so your project needs output: "server" and an adapter (Node, Cloudflare, …)

Install EmDash together with its required peer dependencies. React powers the admin UI at /_emdash/admin; it is required even if your site itself doesn’t use React.

Terminal window
npm install emdash @astrojs/react react react-dom

For a local SQLite database (the default for development), also install the driver:

Terminal window
npm install better-sqlite3

Deploying to Cloudflare? Add the Cloudflare packages as well — the Deploy to Cloudflare guide covers them in detail:

Terminal window
npm install @astrojs/cloudflare @emdash-cms/cloudflare

Add both react() and emdash() to your integrations array. Registering @astrojs/react is not optional: installing the package alone is not enough, and without the integration the admin builds fine but never hydrates — the page sits on “Loading EmDash…” forever.

astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import react from "@astrojs/react";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
integrations: [
react(),
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});

Create src/live.config.ts so Astro’s content layer can resolve EmDash content. Without it, getEmDashCollection / getEmDashEntry have no live collection to route through.

src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

Your existing src/content.config.ts (file-based collections) keeps working alongside it — see EmDash for Astro Developers for how the two coexist.

  1. Start the dev server:

    Terminal window
    npm run dev
  2. Open http://localhost:4321/_emdash/admin and complete the Setup Wizard.

  3. Create and publish a post, then query it from a page:

    src/pages/test.astro
    ---
    import { getEmDashCollection } from "emdash";
    const { entries: posts } = await getEmDashCollection("posts", {
    status: "published",
    });
    ---
    <ul>{posts.map((post) => <li>{post.data.title}</li>)}</ul>

Follow Deploy to Cloudflare for the full setup (D1 database, R2 media bucket, cron triggers). Two things trip up existing projects in particular:

  • Use Cloudflare Workers, not Pages. The @astrojs/cloudflare adapter emits a wrangler.json that Pages does not accept. If your site currently deploys to Pages, migrate it to Workers first.
  • Bindings must exist in your wrangler.jsonc. At minimum you need a D1 binding for the database and an R2 binding for media, matching the binding names in your astro.config.mjs.
SymptomCauseFix
Admin stuck on “Loading EmDash…”@astrojs/react not registeredAdd react() to integrations
Could not resolve "astro:content" in live.config.tsAstro older than 6Upgrade Astro
getEmDashCollection returns an errorMissing src/live.config.tsAdd the live collections loader
Build errors about unresolved packagesPeer dependencies not installedInstall @astrojs/react, react, react-dom explicitly
Content changes don’t appearPage is prerenderedSet export const prerender = false on dynamic pages