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.
Porting WordPress Plugins to EmDash
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.
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.
Shortcodes → Portable Text Blocks
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()
admin:{portableTextBlocks:[{type:"youtube",label:"YouTube Video",icon:"video",fields:[{type:"text_input",action_id:"id",label:"YouTube URL"},{type:"text_input",action_id:"title",label:"Title"},],}],}// EmDash — Astro component for rendering
// src/astro/YouTube.astro
const{id,title}=Astro.props.node;constvideoId=id?.match(/(?:v=|youtu\.be\/)([^&]+)/)?.[1]??id;// <iframe src={`https://youtube-nocookie.com/embed/${videoId}`} ... />
Options API → Plugin KV
WordPress's get_option/update_option maps to the plugin KV store. The key difference: WordPress options are global, EmDash KV is automatically scoped to the plugin.
WordPress plugins that create custom tables with $wpdb->query("CREATE TABLE ...") should use EmDash's storage collections instead. No migrations needed — declare the schema in definePlugin() and it's automatically provisioned.
// WordPress
$wpdb->insert($table,['form_id'=>$id,'data'=>json_encode($data),'created_at'=>current_time('mysql')]);$results=$wpdb->get_results("SELECT * FROM $table WHERE form_id = '$id' ORDER BY created_at DESC LIMIT 50");// EmDash — declared in definePlugin()
storage:{submissions:{indexes:["formId","createdAt",["formId","createdAt"]],},},// In a hook or route handler
awaitctx.storage.submissions!.put(entryId,{formId,data,createdAt: newDate().toISOString()});constresult=awaitctx.storage.submissions!.query({where:{formId},orderBy:{createdAt:"desc"},limit: 50,});
Seeding Data (replaces starter content, theme setup)
WordPress plugins that call wp_insert_term(), register_nav_menu(), or insert default content on activation should use a seed file: