Architecture

On this page

EmDash runs inside an Astro application. The public pages and the admin panel share the EmDash runtime, database, and media storage. They are parts of one deployed application rather than a frontend and a separate CMS service.

The parts of an EmDash site

The following table shows how each part connects through the EmDash runtime:

PartActionConnection
Admin panelSaves entries and mediaSends changes to the EmDash runtime
Astro pages and componentsQuery entries for the public siteRead content through the EmDash runtime
EmDash runtimeApplies the content model, publishing rules, queries, plugins, and API behaviorReads and writes the database and media storage
SQL databaseStores the content model, entries, users, settings, and other recordsUsed by the EmDash runtime
Media storageStores uploaded filesUsed by the EmDash runtime

Editors use the admin panel to work with content. Site developers decide how that content appears by writing Astro pages and components. Both sides use the same content model: the collection and field definitions that describe each entry.

The database stores the content model, entries, users, settings, and other CMS records. The media storage adapter holds uploaded files. A media field in an entry refers to a stored media item rather than placing the file bytes in the content table.

What Astro configures

An EmDash site needs server rendering because the admin, API routes, and current content are served at runtime. Configure Astro with output: "server" and an adapter for the deployment platform.

Register both React and EmDash in the Astro integrations array. React hydrates the admin panel; if @astrojs/react is installed but react() is missing from the array, the admin page remains on Loading EmDash….

The following Node.js example supplies a SQLite database and local media storage:

import node from "@astrojs/node";
import react from "@astrojs/react";
import { defineConfig } from "astro/config";
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",
			}),
		}),
	],
});

The integration can use other supported database and storage adapters. A local storage adapter is the default when storage is omitted, but production deployments need storage that persists across application releases and instances. See Configuration for the available adapters and options.

How pages query content

src/live.config.ts registers the EmDash loader as an Astro Live Content Collection:

import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

Pages then call getEmDashCollection() for a list or getEmDashEntry() for one entry. The following query loads posts when the page renders:

---
import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) throw error;
---

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

A server-rendered page runs this query for incoming requests, subject to any cache you configure. A prerendered page runs it during the build and cannot show later edits until another build.

How the model changes

Administrators can create collections and fields in the admin panel. EmDash changes the database schema so later entries and queries use the new model. Seed files provide a version-controlled way to describe a starting model for another environment, and generated TypeScript declarations help developers use the current model in code.

These tools describe and change the same model; they do not create parallel copies. Read Content model for the workflow and data-safety boundaries.

How plugins extend the runtime

Plugins can respond to content and media events and add API routes, settings, or admin interfaces. Native plugins run with the host application’s access. Standard-format plugins can run in an isolated runtime when the site configures a sandbox runner and grants capabilities. Read Choose a plugin format before installing or building one.