Port a WordPress plugin to EmDash CMS. Use this skill when asked to migrate, convert, or port a WordPress plugin, theme functionality, or custom post type to EmDash. Provides concept mapping and implementation patterns.
This skill maps WordPress concepts to their EmDash equivalents for plugin porting. For general plugin authoring details (plugin structure, definePlugin(), hooks, storage, admin UI, etc.), use the creating-plugins skill.
| WordPress | EmDash | Notes |
|---|
register_post_type() | SchemaRegistry.createCollection() | Via Admin API or seed file |
register_taxonomy() | _emdash_taxonomy_defs table | Hierarchical or flat, attached to collections |
register_meta() / ACF | Collection fields via SchemaRegistry | All become typed schema fields |
get_post_meta() | entry.data.fieldName | Direct typed access |
get_option() | getSiteSetting() / ctx.kv | Site settings or plugin-namespaced KV |
WP_Query | getEmDashCollection() | Runtime queries with filters |
get_post($id) | getEmDashEntry(collection, slug) | Returns entry or null |
wp_insert_post() | POST /_emdash/api/content/{type} | REST API |
wp_update_post() | PUT /_emdash/api/content/{type}/{id} | REST API |
wp_delete_post() | DELETE /_emdash/api/content/{type}/{id} | Soft delete |
| Custom tables | Plugin storage collections | ctx.storage.collectionName.put/get/query |
| WordPress | EmDash | Notes |
|---|---|---|
get_bloginfo('name') | getSiteSetting('title') | From options table with site: prefix |
get_option('blogdesc') | getSiteSetting('tagline') | Site settings API |
| Theme Customizer | Site Settings admin page | /_emdash/admin/settings |
site_icon | getSiteSetting('favicon') | Media reference |
custom_logo | getSiteSetting('logo') | Media reference |
| WordPress | EmDash | Notes |
|---|---|---|
register_nav_menu() | Create menu via admin or seed | _emdash_menus table |
wp_nav_menu() | getMenu(name) | Returns { items: MenuItem[] } |
wp_nav_menu_item | _emdash_menu_items table | Type: custom, page, post, taxonomy |
_menu_item_object_id | reference_id + reference_collection | Links to content entries |
| Menu locations | Query by name in templates | No locations concept — direct query |
| WordPress | EmDash | Notes |
|---|---|---|
register_taxonomy() | _emdash_taxonomy_defs table | Define via admin, seed, or API |
get_terms() | getTaxonomyTerms(name) | Returns tree for hierarchical |
get_the_terms() | getEntryTerms(collection, id, name) | Terms for specific entry |
wp_set_post_terms() | TaxonomyRepository.setTermsForEntry() | Replace terms for entry |
| Hierarchical taxonomy | hierarchical: true in definition | Categories-style |
| Flat taxonomy | hierarchical: false | Tags-style |
| WordPress | EmDash | Notes |
|---|---|---|
register_sidebar() | _emdash_widget_areas table | Create via admin or seed |
dynamic_sidebar() | getWidgetArea(name) | Returns { widgets: Widget[] } |
WP_Widget class | Widget types: content, menu, component | Simplified — 3 types only |
| Text widget | type: 'content' + Portable Text | Rich text widget |
| Nav Menu widget | type: 'menu' + menuName | References a menu |
| Custom widgets | type: 'component' + componentId | Plugin-registered components |
| WordPress | EmDash | Notes |
|---|---|---|
add_menu_page() | admin.pages in definePlugin() | Plugin config |
add_submenu_page() | Nested admin pages | Parent determines hierarchy |
add_settings_section() | admin.settingsSchema | Auto-generated settings page |
add_meta_box() | Field groups in collection schema | UI config in schema |
wp_enqueue_script() | ESM imports in admin components | React (trusted) or Block Kit (sandboxed) |
| Admin notices | Toast notifications | Via admin UI framework |
| WordPress | EmDash | Notes |
|---|---|---|
add_action('init') | plugin:install hook | Runs once on first install |
add_action('save_post') | content:afterSave hook | Filter by event.collection |
add_action('before_delete_post') | content:beforeDelete hook | Return false to prevent |
add_action('wp_head') | page:metadata / page:fragments hook | Metadata is sandbox-safe; scripts need trusted plugin |
add_action('rest_api_init') | definePlugin({ routes }) | Trusted only |
add_filter('the_content') | Portable Text components | Custom block renderers |
add_filter('the_title') | Template logic | Handle in Astro component |
| WordPress | EmDash | Notes |
|---|---|---|
add_shortcode() | Portable Text custom block | Content → block. Template → component. Trusted only. |
register_block_type() | PT block + componentsEntry | Block data → Astro component props. Trusted only. |
| Template tags | Astro expressions | get_the_title() → {post.data.title} |
| Widgets | Widget area + components | Query with getWidgetArea() |
| WordPress | EmDash | Notes |
|---|---|---|
get_option('plugin_*') | ctx.kv.get(key) | Namespaced to plugin automatically |
update_option() | ctx.kv.set(key, value) | Scoped KV storage |
delete_option() | ctx.kv.delete(key) | Delete single key |
| Custom tables | ctx.storage.collection | Document collections with indexes |
| Transients | Plugin KV | No TTL yet |
These patterns cover WordPress-specific concepts that don't have a direct 1:1 mapping. For general plugin patterns (defining hooks, storage, routes, admin UI), see the creating-plugins skill.
WordPress shortcodes ([youtube id="xxx"]) become Portable Text custom block types. The block data replaces shortcode attributes, and an Astro component replaces the shortcode render function. This is a trusted-only feature.
// WordPress
add_shortcode('youtube', function($atts) {
return '<iframe src="https://youtube.com/embed/' . $atts['id'] . '"></iframe>';
});
// EmDash — block type declaration in definePlugin()