Skip to content
8 changes: 8 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ export default defineConfig({
collapsed: true,
items: [
{ label: "Contributor Guide", slug: "contributing" },
{
label: "Architecture (internals)",
slug: "contributing/architecture",
},
{
label: "Documentation Style Guide",
slug: "contributing/docs-style-guide",
},
{ label: "Translating EmDash", slug: "contributing/translating" },
],
},
Expand Down
31 changes: 16 additions & 15 deletions docs/src/content/docs/coming-from/astro-for-wp-devs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ WordPress themes have a flat structure with magic filenames. Astro uses explicit
| `style.css` | `src/styles/` | Global CSS |
| `functions.php` | `astro.config.mjs` | Site configuration |

A typical Astro project:
The following tree shows a typical Astro project layout:

```
src/
Expand All @@ -64,6 +64,8 @@ src/
1. **Frontmatter** (between `---` fences) — Server-side code, like PHP at the top of a template
2. **Template** — HTML with expressions, like the rest of a PHP template

The following component declares typed props in the frontmatter and renders them in the template:

```astro title="src/components/PostCard.astro"
---
// Frontmatter: runs on server, never sent to browser
Expand Down Expand Up @@ -183,7 +185,7 @@ const { title, featured = false } = Astro.props;
</article>
```

Usage:
The following markup uses that component, passing the default slot and a named `footer` slot:

```astro
<Card title="Hello" featured>
Expand Down Expand Up @@ -249,7 +251,7 @@ The difference: slots receive child elements at the call site, while WordPress h

## Layouts

Layouts wrap pages with common HTML structure—the `<head>`, header, footer, and anything shared across pages. This replaces `header.php` + `footer.php`.
Layouts wrap pages with common HTML structure—the `<head>`, header, footer, and anything shared across pages. This replaces `header.php` + `footer.php`. The following layout defines that shared shell and exposes a slot for page content:

```astro title="src/layouts/Base.astro"
---
Expand Down Expand Up @@ -329,7 +331,7 @@ Styles in a `<style>` tag are automatically scoped to that component:
</style>
```

The generated HTML includes unique class names to prevent style leakage. No more specificity wars.
The generated HTML includes unique class names to prevent style leakage, so component styles stay contained without escalating selector specificity.

### Global Styles

Expand Down Expand Up @@ -402,9 +404,9 @@ For simple interactions, add a `<script>` tag:

Scripts are bundled and deduplicated automatically. If this component appears twice on a page, the script runs once.

### Advanced: Interactive Components
### Advanced interactive components

For more complex interactivity, Astro can load JavaScript components (React, Vue, Svelte) on demand. This is optional—most sites work fine with just `<script>` tags.
For more complex interactivity, Astro can load JavaScript components (React, Vue, Svelte) on demand. This is optional—most sites work fine with just `<script>` tags. The following page loads a component only when it scrolls into view:

```astro title="src/pages/index.astro"
---
Expand Down Expand Up @@ -475,8 +477,7 @@ const { post } = Astro.props;
| `add_rewrite_rule()` | Create files or folders |

<Aside type="tip">
No more guessing which template WordPress will use. The URL `/posts/hello` always loads
`src/pages/posts/[slug].astro`.
Routing is explicit: the URL `/posts/hello` always loads `src/pages/posts/[slug].astro`.
</Aside>

## Where WordPress Concepts Live
Expand Down Expand Up @@ -528,14 +529,14 @@ A reference for finding the Astro/EmDash equivalent of WordPress features:
| Featured image | Media reference field |
| Gutenberg blocks | Portable Text blocks |

## Summary
## Concept Mapping

The jump from WordPress to Astro is significant but logical:
The main WordPress-to-Astro shifts covered in this guide:

1. **PHP templates Astro components** — Same idea (server code + HTML), better organization
2. **Template tags → Props and imports** — Explicit data flow instead of globals
3. **Theme files → Pages directory** — URLs match file structure
4. **Hooks → Slots and middleware** — More predictable insertion points
5. **jQuery by default → Zero JS by default** — Add interactivity intentionally
- PHP templates become Astro components: server code plus HTML, with explicit file organization.
- Template tags become props and imports: data flows through arguments instead of globals.
- Theme files become a pages directory: URLs match the file structure.
- Hooks become slots and middleware: insertion points are defined where content is passed in.
- jQuery loads by default in WordPress; Astro ships no JavaScript until you add it.

Start with the [Getting Started](/getting-started/) guide to build your first EmDash site, or explore [Working with Content](/guides/working-with-content/) to learn how to query and render CMS data.
26 changes: 18 additions & 8 deletions docs/src/content/docs/coming-from/astro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ description: Add WordPress-style CMS features to your Astro site with EmDash

import { Aside, Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";

EmDash is a CMS built specifically for Astro—not a generic headless CMS with an Astro adapter. It extends your Astro site with database-backed content, a polished admin UI, and WordPress-style features (menus, widgets, taxonomies) while preserving the developer experience you expect.
EmDash is a CMS built specifically for Astro. It extends your Astro site with database-backed content, a polished admin UI, and WordPress-style features (menus, widgets, taxonomies) while preserving the developer experience you expect.

Everything you know about Astro still applies. EmDash enhances your site; it doesn't replace your workflow.
Everything you know about Astro still applies. EmDash adds content management on top of your existing Astro workflow.

## What EmDash Adds

Expand All @@ -16,7 +16,7 @@ EmDash provides the content management features that file-based Astro sites lack
| Feature | Description |
| -------------------- | ---------------------------------------------------- |
| **Admin UI** | Full WYSIWYG editing interface at `/_emdash/admin` |
| **Database storage** | Content stored in SQLite, libSQL, or Cloudflare D1 |
| **Database storage** | Content stored in SQLite, libSQL, Cloudflare D1, or PostgreSQL |
| **Media library** | Upload, organize, and serve images and files |
| **Navigation menus** | Drag-and-drop menu management with nesting |
| **Widget areas** | Dynamic sidebars and footer regions |
Expand All @@ -26,8 +26,8 @@ EmDash provides the content management features that file-based Astro sites lack
| **Revisions** | Content version history |

<Aside type="tip">
Content changes appear immediately without rebuilding your site. EmDash sites run in SSR mode by
default.
EmDash sites run in SSR mode by default, so content is served at runtime and changes appear
immediately.
</Aside>

## Astro Collections vs EmDash
Expand All @@ -36,7 +36,7 @@ Astro's `astro:content` collections are file-based and resolved at build time. E

| | Astro Collections | EmDash Collections |
| ------------------ | ------------------------------------ | ------------------------------------ |
| **Storage** | Markdown/MDX files in `src/content/` | SQLite/D1 database |
| **Storage** | Markdown/MDX files in `src/content/` | SQL database (SQLite, libSQL, D1, or Postgres) |
| **Editing** | Code editor | Admin UI |
| **Content format** | Markdown with frontmatter | Portable Text (structured JSON) |
| **Updates** | Requires rebuild | Instant (SSR) |
Expand Down Expand Up @@ -69,6 +69,8 @@ EmDash requires two configuration files.

### Astro Integration

The following configuration registers EmDash as an Astro integration in server output mode:

```ts title="astro.config.mjs"
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
Expand All @@ -90,6 +92,8 @@ export default defineConfig({

### Live Collections Loader

The following file registers EmDash as a live content source:

```ts title="src/live.config.ts"
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
Expand All @@ -101,7 +105,7 @@ export const collections = {
};
```

This registers EmDash as a live content source. The `_emdash` collection internally routes to your content types (posts, pages, products).
The `_emdash` collection internally routes to your content types (posts, pages, products).

## Querying Content

Expand Down Expand Up @@ -204,6 +208,8 @@ EmDash provides APIs for WordPress-style features that don't exist in Astro's co

### Navigation Menus

The following layout fetches a menu by location and renders it with nested items:

```astro title="src/layouts/Base.astro"
---
import { getMenu } from "emdash";
Expand Down Expand Up @@ -233,6 +239,8 @@ const primaryMenu = await getMenu("primary");

### Widget Areas

The following layout fetches a widget area and renders each widget:

```astro title="src/layouts/BlogPost.astro"
---
import { getWidgetArea } from "emdash";
Expand All @@ -257,6 +265,8 @@ const sidebar = await getWidgetArea("sidebar");

### Site Settings

The following component reads global site settings and renders a logo or title:

```astro title="src/components/Header.astro"
---
import { getSiteSettings, getSiteSetting } from "emdash";
Expand Down Expand Up @@ -320,7 +330,7 @@ export default definePlugin({

## Server Rendering

EmDash sites run in SSR mode. Content changes appear immediately without rebuilds.
EmDash sites run in SSR mode, so content is served at runtime and changes appear immediately.

For static pages with `getStaticPaths`, content is fetched at build time:

Expand Down
31 changes: 19 additions & 12 deletions docs/src/content/docs/coming-from/wordpress.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ where: { category: "news" },

### Getting a Single Entry

The following examples fetch one entry by identifier and render it, in WordPress and in EmDash:

<Tabs>
<TabItem label="WordPress">
```php title="single.php"
Expand All @@ -134,7 +136,8 @@ import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);

## if (!post) return Astro.redirect("/404");
if (!post) return Astro.redirect("/404");
---

<article>
<h1>{post.data.title}</h1>
Expand Down Expand Up @@ -198,6 +201,8 @@ const { post } = Astro.props;
</article>
```

The following page imports that component and renders it for each post:

```astro title="src/pages/index.astro"
---
import PostCard from "../components/PostCard.astro";
Expand Down Expand Up @@ -233,7 +238,8 @@ wp_nav_menu([
---
import { getMenu } from "emdash";

## const menu = await getMenu("primary");
const menu = await getMenu("primary");
---

<nav>
<ul>
Expand Down Expand Up @@ -270,7 +276,8 @@ Widget areas work like sidebars in WordPress:
import { getWidgetArea } from "emdash";
import { PortableText } from "emdash/ui";

## const sidebar = await getWidgetArea("sidebar");
const sidebar = await getWidgetArea("sidebar");
---

{sidebar && (

Expand Down Expand Up @@ -299,6 +306,8 @@ Site options and customizer settings map to `getSiteSetting()`:
| `get_option('date_format')` | `getSiteSetting("dateFormat")` |
| `home_url()` | `Astro.site` |

The following example reads individual settings with `getSiteSetting()`:

```ts
import { getSiteSetting } from "emdash";

Expand Down Expand Up @@ -339,29 +348,27 @@ WordPress hooks (`add_action`, `add_filter`) become EmDash plugin hooks:

<CardGrid>
<Card title="Type Safety" icon="seti:typescript">
TypeScript throughout. Collections, queries, and components are fully typed. No more guessing
field names or return types.
TypeScript throughout. Collections, queries, and components are fully typed, so field names and
return types autocomplete and are checked at build time.
</Card>
<Card title="Performance" icon="rocket">
No PHP overhead. Static generation by default. Server rendering when needed. Edge deployment
ready.
Static generation by default, with server rendering when needed. Edge deployment ready.
</Card>
<Card title="Modern DX" icon="laptop">
Hot module replacement. Component-based architecture. Modern tooling (Vite, TypeScript, ESLint).
</Card>
<Card title="Git-based Deployments" icon="github">
Code and templates in git. Content in the database. No FTP, no file permissions, no hacked
sites.
Code and templates live in git; content lives in the database. Deploy by pushing code.
</Card>
</CardGrid>

### Preview Links

EmDash generates secure preview URLs with HMAC-signed tokens. Content editors can preview drafts without logging into production—share a link, not credentials.
EmDash generates secure preview URLs with HMAC-signed tokens. Content editors share a preview link to show a draft, so reviewers can see it without a production login.

### No Plugin Conflicts
### Isolated Plugins

WordPress plugin conflicts disappear. EmDash plugins run in isolated contexts with explicit APIs. No global state pollution.
EmDash plugins run in isolated contexts with explicit APIs. Each plugin reaches only the APIs it declares, so plugins do not share or overwrite each other's global state.

## Content Editor Experience

Expand Down
Loading
Loading