15 KiB
WP Theme → EmDash Concept Mapping
Template Hierarchy
| WP Template | Purpose | EmDash Equivalent |
|---|---|---|
index.php |
Fallback for everything | src/pages/index.astro |
front-page.php |
Static front page | src/pages/index.astro |
home.php |
Blog posts page | src/pages/index.astro or src/pages/blog/index.astro |
single.php |
Single post | src/pages/posts/[slug].astro |
single-{post_type}.php |
Custom post type single | src/pages/{type}/[slug].astro |
page.php |
Single page | src/pages/pages/[slug].astro |
page-{slug}.php |
Specific page template | src/pages/pages/{slug}.astro (static) |
archive.php |
Post archives | src/pages/posts/index.astro |
archive-{post_type}.php |
CPT archive | src/pages/{type}/index.astro |
category.php |
Category archive | src/pages/categories/[slug].astro |
tag.php |
Tag archive | src/pages/tags/[slug].astro |
author.php |
Author archive | src/pages/authors/[slug].astro |
date.php |
Date archive | src/pages/archive/[year]/[month].astro |
search.php |
Search results | src/pages/search.astro (use search() API) |
404.php |
Not found | src/pages/404.astro |
header.php |
Site header | Part of src/layouts/Base.astro |
footer.php |
Site footer | Part of src/layouts/Base.astro |
sidebar.php |
Sidebar widget area | Component: src/components/Sidebar.astro |
comments.php |
Comments template | Component or third-party (Giscus, etc.) |
Template Parts
| WP Pattern | EmDash Pattern |
|---|---|
get_template_part('content', 'post') |
<PostCard /> component |
get_template_part('template-parts/header/site-branding') |
<SiteBranding /> component |
template-parts/ directory |
src/components/ directory |
Functions.php Registrations
Navigation Menus
// WordPress
register_nav_menus([
'primary' => 'Primary Menu',
'footer' => 'Footer Menu',
]);
EmDash has first-class menu support with automatic URL resolution:
import { getMenu } from "emdash";
const primaryMenu = await getMenu("primary");
// Returns { id, name, label, items: MenuItem[] }
// Items have resolved URLs and support nesting
Menus are created via:
- Admin UI
- Seed files (JSON)
- WordPress import (automatic migration)
Sidebars/Widget Areas
// WordPress
register_sidebar([
'name' => 'Main Sidebar',
'id' => 'sidebar-1',
]);
EmDash has first-class widget area support:
import { getWidgetArea } from "emdash";
const sidebar = await getWidgetArea("sidebar");
// Returns { id, name, label, widgets: Widget[] }
// Widgets can be content (Portable Text), menu, or component
Widget areas are created via:
- Admin UI
- Seed files (JSON)
- WordPress import (automatic migration)
Theme Support
// WordPress
add_theme_support('post-thumbnails');
add_theme_support('title-tag');
add_theme_support('custom-logo');
add_theme_support('post-formats');
EmDash equivalents:
post-thumbnails→featured_imagefield on collections (automatic)title-tag→ Astro handles<title>in layoutcustom-logo→getSiteSetting("logo")returns{ mediaId, alt, url }post-formats→ Field on collection (select type)
Custom Post Types
// WordPress
register_post_type('portfolio', [...]);
EmDash: Create collection via admin UI or API. The collection will be created during content import if it doesn't exist.
Template Tags → EmDash
Content Retrieval
| WP Function | EmDash Equivalent |
|---|---|
have_posts() / the_post() |
getEmDashCollection() |
get_post() |
getEmDashEntry() |
the_title() |
post.data.title |
the_content() |
<PortableText value={post.data.content} /> |
the_excerpt() |
post.data.excerpt |
the_permalink() |
/posts/${post.id} or /posts/${post.data.slug} |
the_post_thumbnail() |
post.data.featured_image |
get_the_date() |
post.data.publishedAt |
get_the_author() |
post.data.byline?.displayName |
get_the_category() |
getEntryTerms(coll, id, "categories") |
get_the_tags() |
getEntryTerms(coll, id, "tags") |
Taxonomies
| WP Function | EmDash Equivalent |
|---|---|
get_categories() |
getTaxonomyTerms("categories") |
get_tags() |
getTaxonomyTerms("tags") |
get_terms($taxonomy) |
getTaxonomyTerms(taxonomy) |
get_term($id, $taxonomy) |
getTerm(taxonomy, slug) |
get_term_by('slug', ...) |
getTerm(taxonomy, slug) |
get_the_terms($post, $tax) |
getEntryTerms(collection, entryId, taxonomy) |
wp_get_post_categories() |
getEntryTerms(collection, entryId, "categories") |
wp_get_post_tags() |
getEntryTerms(collection, entryId, "tags") |
get_category_link($cat) |
/categories/${term.slug} |
get_tag_link($tag) |
/tags/${term.slug} |
EmDash supports hierarchical taxonomies (like categories) and flat taxonomies (like tags):
Site Info
| WP Function | EmDash Equivalent |
|---|---|
bloginfo('name') |
getSiteSetting("title") |
bloginfo('description') |
getSiteSetting("tagline") |
home_url() |
Astro.site or import.meta.env.SITE |
get_theme_mod() |
getSiteSetting(key) or plugin storage |
get_option() |
getSiteSetting(key) or plugin storage |
get_custom_logo() |
getSiteSetting("logo") returns URL |
Conditional Tags
| WP Function | Astro Equivalent |
|---|---|
is_home() |
Astro.url.pathname === '/' |
is_front_page() |
Astro.url.pathname === '/' |
is_single() |
Check route pattern |
is_page() |
Check route pattern |
is_archive() |
Check route pattern |
is_category() |
Check route pattern |
is_search() |
Astro.url.pathname === '/search' |
is_404() |
N/A (404.astro handles this) |
Media
| WP Function | EmDash Equivalent |
|---|---|
wp_get_attachment_image() |
<img src={media.url} /> |
wp_get_attachment_url() |
media.url |
the_post_thumbnail() |
post.data.featured_image |
Navigation
| WP Function | EmDash Equivalent |
|---|---|
wp_nav_menu() |
getMenu("menu-name") + render items |
wp_list_pages() |
Query pages collection or use menu |
the_posts_navigation() |
Custom pagination component |
the_posts_pagination() |
Custom pagination component |
get_nav_menu_items() |
getMenu("name").items |
Hooks → EmDash Events
WordPress hooks don't have direct equivalents. Most hook functionality becomes:
- Astro middleware - For request/response modification
- EmDash plugin hooks - For content lifecycle events
- Build-time logic - In Astro config or components
| WP Hook | EmDash Approach |
|---|---|
wp_head |
Add to <head> in layout |
wp_footer |
Add before </body> in layout |
the_content filter |
PortableText components |
pre_get_posts |
Query filters in getEmDashCollection() |
save_post |
EmDash plugin hook: content:beforeSave |
Asset Enqueueing
// WordPress
wp_enqueue_style('theme-style', get_stylesheet_uri());
wp_enqueue_script('theme-script', get_template_directory_uri() . '/js/main.js');
Astro:
---
// In layout or component
import '../styles/main.css';
import '../scripts/main.js';
---
<link rel="stylesheet" href="/styles/main.css" />
<script src="/scripts/main.js"></script>
Or use Astro's built-in bundling:
<style>
/* Scoped styles */
</style>
<script>
// Client-side JS
</script>
Shortcodes → Portable Text Blocks
// WordPress shortcode
add_shortcode('gallery', function($atts) {
return '<div class="gallery">...</div>';
});
// Usage: [gallery ids="1,2,3"]
EmDash: Custom Portable Text block type + component:
---
// GalleryBlock.astro
const { ids } = Astro.props;
---
<div class="gallery">
<!-- Render images -->
</div>
<PortableText
value={content}
components={{ gallery: GalleryBlock }}
/>
Widgets → Widget Areas
EmDash has first-class widget support with getWidgetArea():
import { getWidgetArea } from "emdash";
const sidebar = await getWidgetArea("sidebar");
sidebar?.widgets.forEach((widget) => {
// widget.type: "content" | "menu" | "component"
});
Widget Types
| WP Widget | EmDash Widget Type | Notes |
|---|---|---|
| Text/HTML | content |
Portable Text (rich content) |
| Custom Menu | menu |
References menu by name |
| Recent Posts | component core:recent-posts |
Built-in component with props |
| Categories | component core:categories |
Built-in component |
| Tag Cloud | component core:tag-cloud |
Built-in component |
| Search | <LiveSearch /> component |
Use emdash/ui LiveSearch |
| Archives | component core:archives |
Built-in component |
Core Widget Components
| Component ID | Props |
|---|---|
core:recent-posts |
limit, collection |
core:categories |
taxonomy, showCounts |
core:tag-cloud |
taxonomy, limit |
core:search |
placeholder |
core:archives |
collection, format |
Search
WordPress search maps to EmDash's FTS5-based search system:
// WordPress search form
get_search_form();
// WordPress search query
$results = new WP_Query(['s' => 'hello world']);
EmDash:
import { search } from "emdash";
import LiveSearch from "emdash/ui/search";
// Programmatic search
const results = await search("hello world", {
collections: ["posts", "pages"],
limit: 20,
});
// Or use the LiveSearch component
<LiveSearch placeholder="Search..." />
Search Page Pattern
---
// src/pages/search.astro
import { search } from "emdash";
import Base from "../layouts/Base.astro";
const query = Astro.url.searchParams.get("q") || "";
const results = query ? await search(query, { limit: 20 }) : { results: [] };
---
<Base title={`Search: ${query}`}>
<h1>Search Results for "{query}"</h1>
{results.results.length === 0 ? (
<p>No results found.</p>
) : (
<ul>
{results.results.map(r => (
<li>
<a href={`/${r.collection}/${r.slug}`}>{r.title}</a>
<p set:html={r.snippet} />
</li>
))}
</ul>
)}
</Base>
Search Features
| WordPress | EmDash |
|---|---|
| Basic keyword search | FTS5 with Porter stemming |
| Search all public post types | Per-collection search enable |
s query parameter |
q query parameter |
| Relevance sorting | BM25 ranking with field weights |
| Search widget | <LiveSearch /> component |
Note: Search must be enabled per-collection in admin. Mark fields as "Searchable" to include them in the index.
Reusable Blocks → Sections
WordPress reusable blocks (wp_block post type) map to EmDash sections:
// WordPress - creating a reusable block
// Done via Gutenberg editor, saved as wp_block post type
EmDash:
import { getSection, getSections } from "emdash";
// Get a specific section
const cta = await getSection("newsletter-cta");
// List sections by category
const heroes = await getSections({ category: "heroes" });
Inserting Sections
In WordPress, you insert reusable blocks from the block inserter. In EmDash, editors use the /section slash command in the rich text editor.
Section Sources
| Source | Origin |
|---|---|
theme |
Defined in seed file (theme patterns) |
user |
Created by editors in admin |
import |
Imported from WordPress reusable blocks |
Migration
WordPress wp_block posts are automatically imported as sections:
- Content converted from Gutenberg to Portable Text
- Placed in "Imported" category
- Source set to
"import"