Migrate content
Export, import, and verify a WordPress site.
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.
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.
| WordPress | EmDash and Astro |
|---|---|
| Post type | EmDash collection |
| Post meta | Collection field |
| Category or tag | EmDash taxonomy |
WP_Query |
getEmDashCollection() |
get_post() |
getEmDashEntry() |
the_content() |
<PortableText /> |
| Template hierarchy | Files in src/pages/ |
| Template part | Imported .astro component |
header.php and footer.php |
Astro layout |
wp_nav_menu() |
getMenu() |
| Sidebar | Widget area and <WidgetArea /> |
| Options API | Site settings, or a plugin’s ctx.kv |
| WordPress plugin | Sandboxed or native EmDash plugin |
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>))}Astro routes replace the WordPress template hierarchy. A template chooses its own URL structure rather than relying on magic filenames.
| WordPress file | Current blog-template file |
|---|---|
front-page.php or home.php |
src/pages/index.astro |
single.php |
src/pages/posts/[slug].astro |
archive.php |
src/pages/posts/index.astro |
page.php |
src/pages/pages/[slug].astro |
category.php |
src/pages/category/[slug].astro |
tag.php |
src/pages/tag/[slug].astro |
search.php |
src/pages/search.astro |
404.php |
src/pages/404.astro |
header.php and footer.php |
src/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>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: [] };EmDash has two plugin formats:
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.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.
Open Import WordPress in the admin sidebar, or visit /_emdash/admin/import/wordpress directly. EmDash supports two import paths:
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.
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.
Migrate content
Export, import, and verify a WordPress site.
Port a theme
Convert template files and dynamic features to an Astro template.
Port a plugin
Choose a plugin format and map hooks, storage, routes, and UI.