EmDash for WordPress Developers

On this page

EmDash keeps familiar content concepts such as posts, pages, taxonomies, menus, media, and revisions. Astro supplies the theme layer: routes, layouts, components, and server rendering.

What stays familiar

Editors still work with named content types, structured fields, draft and published states, hierarchical categories, flat tags, nested menus, media, and revision history. Those concepts move into EmDash collections and management screens.

The development workflow changes. PHP template selection becomes explicit Astro routes, template parts become imported components, and code is deployed separately from database content. You do not need React to build the public site; .astro components combine server-side TypeScript with HTML-like templates.

Concept map

WordPressEmDash and Astro
Post typeEmDash collection
Post metaCollection field
Category or tagEmDash taxonomy
WP_QuerygetEmDashCollection()
get_post()getEmDashEntry()
the_content()<PortableText />
Template hierarchyFiles in src/pages/
Template partImported .astro component
header.php and footer.phpAstro layout
wp_nav_menu()getMenu()
SidebarWidget area and <WidgetArea />
Options APISite settings, or a plugin’s ctx.kv
WordPress pluginSandboxed or native EmDash plugin

Content model

Create and edit collections under Content Types in the EmDash admin. A collection has explicit typed fields and may enable drafts, revisions, scheduling, search, SEO, or comments.

Queries return Astro live-collection entries. entry.id is the route identifier and is normally the slug; entry.data.id is the database content ID.

The following archive queries posts in publication order:

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

const { entries: posts, error } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
  limit: 10,
});

if (error) return new Response("Could not load posts", { status: 500 });
---

{posts.map((post) => (
  <article>
    <h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
    {post.data.excerpt && <p>{post.data.excerpt}</p>}
  </article>
))}

Theme files

Astro routes replace the WordPress template hierarchy. A template chooses its own URL structure rather than relying on magic filenames.

WordPress fileCurrent blog-template file
front-page.php or home.phpsrc/pages/index.astro
single.phpsrc/pages/posts/[slug].astro
archive.phpsrc/pages/posts/index.astro
page.phpsrc/pages/pages/[slug].astro
category.phpsrc/pages/category/[slug].astro
tag.phpsrc/pages/tag/[slug].astro
search.phpsrc/pages/search.astro
404.phpsrc/pages/404.astro
header.php and footer.phpsrc/layouts/Base.astro

Read Astro for WordPress developers for the project structure, component syntax, props, slots, layouts, routing, and server-rendered query flow used by EmDash templates.

Menus and widget areas live in the database and remain editable after setup. Templates query them at request time:

---
import { getMenu } from "emdash";
import { WidgetArea } from "emdash/ui";

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

<nav>
  {primary?.items.map((item) => <a href={item.url}>{item.label}</a>)}
</nav>

<aside><WidgetArea name="sidebar" /></aside>

Site settings and taxonomies

Customizer-style site identity lives in EmDash settings. Fetch the settings once and use the resolved logo URL when one is present:

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

const settings = await getSiteSettings();
---

<a href="/">
  {settings.logo?.url
    ? <img src={settings.logo.url} alt={settings.logo.alt || settings.title} />
    : settings.title}
</a>

Taxonomy terms are separate records rather than values stored directly in entry.data. Resolve a term, then use its slug in a collection filter:

import { getEmDashCollection, getTerm } from "emdash";

const news = await getTerm("category", "news", { includeCounts: false });
const { entries: posts } = news
  ? await getEmDashCollection("posts", { where: { category: news.slug } })
  : { entries: [] };

Plugin formats

EmDash has two plugin formats:

  • A sandboxed plugin uses emdash-plugin.jsonc for its identity and trust contract, plus a default-exported src/plugin.ts object typed with SandboxedPlugin. It can run in an isolated runtime and use Block Kit for admin UI.
  • A native plugin exports a build-time descriptor factory and a runtime createPlugin() function built with definePlugin(). Use this format for React admin components, public Astro components, or page fragments.

Start with Choosing a plugin format. The hook names and PluginContext APIs are shared, but the package and handler shapes are not interchangeable.

Import WordPress content

Open Import WordPress in the admin sidebar, or visit /_emdash/admin/import/wordpress directly. EmDash supports two import paths:

  1. Upload a WordPress eXtended RSS (WXR) file exported from Tools → Export.
  2. Install the EmDash Exporter plugin on the WordPress site and connect with a WordPress application password. This path can include content that a normal public REST API does not expose.

Entering a WordPress URL without the exporter can detect the site and count public posts, pages, and media. That REST probe does not import content.

The importer maps WordPress publish to EmDash published. Draft, pending, private, future, trash, and unknown statuses become drafts. Review imported permissions and publication state before changing DNS or retiring WordPress.

Follow Migrate from WordPress for the complete procedure and retry behavior.

Editor workflow

The EmDash admin provides collection lists, a Portable Text editor, media management, menu editing, taxonomy management, revisions, and preview links. It is not a visual copy of wp-admin: Gutenberg blocks become Portable Text, and the available screens depend on the collections and features configured for the site.

Next steps