{"id":98019,"date":"2026-10-05T22:14:00","date_gmt":"2026-10-06T02:14:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=98019"},"modified":"2026-09-29T07:54:28","modified_gmt":"2026-09-29T11:54:28","slug":"wordpress-emdash-custom-post-types-migration-98019","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/wordpress-emdash-custom-post-types-migration-98019\/","title":{"rendered":"The Counterintuitive Truth About Moving Custom Post Types From WordPress to EmDash"},"content":{"rendered":"<p>You can export every post, page, and custom field from WordPress in about 90 seconds. The WXR file lands in your downloads folder. EmDash&#8217;s import tool accepts it. The data moves.<\/p>\n<p>That is not the problem. The problem is that WordPress stores content as HTML strings in the database, while <a href=\"https:\/\/www.em-dash.com\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">EmDash<\/a> uses portable text \u2014 structured JSON with typed blocks and inline metadata. A custom post type in WordPress is a loose collection of meta fields and raw HTML. A content type in EmDash is a defined schema with typed fields, relationships, and machine-readable structure. You are not migrating data. You are migrating the meaning of that data.<\/p>\n<p>This reframes everything. The technical steps matter less than the conceptual shift. Let&#8217;s walk through what that actually means.<\/p>\n<h2>What Makes Custom Post Types Different in EmDash<\/h2>\n<p>WordPress ships with two content types: posts and pages. Everything else requires a plugin. <a href=\"https:\/\/www.advancedcustomfields.com\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">Advanced Custom Fields<\/a> (ACF) became the de facto standard for adding fields to custom post types. But ACF stores field values as post meta \u2014 key-value pairs in the <code>wp_postmeta<\/code>codecodecode table, where the value is often a serialized PHP array or HTML blob.<\/p>\n<p>EmDash flips this. Content types are native to the system. You define them in the admin interface or, ideally, as code. Each field has a type \u2014 text, image, rich text, relation, repeater \u2014 and the data is stored as structured JSON, not as a flat meta table. The editor renders fields based on their declared type, not on a <code>the_content()<\/code>codecodecode call that dumps everything through a single filter.<\/p>\n<p>The practical difference: a WordPress &#8220;project&#8221; post type with a client name, project date, description, and gallery link stores all of that in four meta rows plus the post content field. An EmDash &#8220;project&#8221; content type stores it as a single JSON object where each field is queryable, filterable, and renderable independently.<\/p>\n<h2>The WordPress-to-EmDash Migration Path<\/h2>\n<h3>Step 1: Map Your Content Types<\/h3>\n<p>Before you touch the import button, list every custom post type in your WordPress site. Products, staff, events, portfolios, case studies, whatever you built with ACF or Custom Post Type UI.<\/p>\n<p>For each one, decide what its EmDash counterpart looks like. You are not looking for a 1:1 field match. You are looking for a field-type match. WordPress stores a WYSIWYG editor field as HTML. EmDash uses portable text \u2014 a block-based JSON format. WordPress stores a date picker as a timestamp string. EmDash stores it as an ISO date string.<\/p>\n<p>This mapping step is where most migrations stall. A WordPress site with 12 custom post types and 8 fields each means 96 field definitions to reconcile. Do the work upfront.<\/p>\n<h3>Step 2: Run the WXR Import<\/h3>\n<p>Export your WordPress site as a WXR file. Tools \u2192 Export \u2192 All Content. Upload it to EmDash&#8217;s import page under the admin panel. Or install the EmDash WordPress export plugin for a direct connection.<\/p>\n<p>The import handles posts, pages, media, and custom post types. It reads Yoast SEO fields if present and maps them to EmDash&#8217;s built-in SEO fields. Custom field data comes through as raw key-value pairs. This is where the structured content gap shows up.<\/p>\n<h3>Step 3: Rebuild the Schema<\/h3>\n<p>After the import, your custom post types exist in EmDash as content types, but their fields may be flattened. Open each content type in the admin panel \u2014 Admin \u2192 Content Types \u2192 select the type.<\/p>\n<p>Add fields that match what ACF provided. A text field for the client name. A portable text field for the description. An image field for the featured project image. A relation field if the project links to team members.<\/p>\n<p>The critical detail: EmDash allows you to define fields inline, but these definitions live in the database by default. For production sites, define content types as code in the Astro configuration. This keeps your schema version-controlled and deployable alongside your theme.<\/p>\n<h3>Step 4: Remap the Content<\/h3>\n<p>The imported content still references fields by their WordPress meta keys. You need to tell EmDash which meta key maps to which field. This is manual. There is no automated ACF-to-EmDash field mapper in version 0.1.0.<\/p>\n<p>For each imported entry, open it in the editor. The raw meta fields appear as a section. Drag them into the corresponding structured field, or copy the values and paste them into the correct editor blocks.<\/p>\n<p>This is slow. A site with 200 custom posts and 10 fields each takes hours. But the result is content that the system understands at the field level \u2014 not HTML that the front end has to parse.<\/p>\n<h2>Handling Complex Content<\/h2>\n<h3>Repeater Fields<\/h3>\n<p>ACF repeater fields store repeating groups of sub-fields. In EmDash, portable text supports nested blocks. A project timeline with dates, titles, and descriptions becomes a block group inside the portable text editor. The migration requires unwinding the serialized repeater data and reconstructing it as blocks.<\/p>\n<h3>Relationships<\/h3>\n<p>Post-to-post relationships in WordPress are stored as post meta with the related post ID. EmDash supports relation fields, but the import <a href=\"https:\/\/overcentral.com\/en\/rascal-does-not-dream-trailer-release-80139\/\" title=\"Rascal Does Not Dream Drops Trailer for Final Film\" data-iacss-internal=\"1\">does not<\/a> auto-link them. After migration, you manually reconnect each relation by selecting the target entry from the relation field picker.<\/p>\n<h3>Media Attachments<\/h3>\n<p>Featured images and gallery attachments transfer during the WXR import. EmDash stores media in R2 (or local storage on a self-hosted Node.js server). The import preserves the file references. You do not need to re-upload everything.<\/p>\n<h2>What Does Not Migrate<\/h2>\n<p>The import tool does not transfer plugins, themes, custom functionality, WooCommerce stores, membership systems, or SEO configurations that depend on third-party plugins. Your custom post type content arrives, but anything that relied on a plugin&#8217;s rendering logic \u2014 a specific ACF field template, a custom taxonomy filter, a front-end display loop \u2014 must be rebuilt as EmDash components or themes.<\/p>\n<p>The WXR export also does not include user roles, comment moderation settings, or site configuration. Those are set up fresh in EmDash.<\/p>\n<h2>Post-Migration Verification<\/h2>\n<p>After the import and field remapping, verify three things:<\/p>\n<ol>\n<li><strong>Field integrity.<\/strong> Open a dozen entries across each content type. Confirm that text fields display text, image fields show the correct media, and portable text blocks render without broken HTML.<\/li>\n<\/ol>\n<ol>\n<li><strong>URL structure.<\/strong> EmDash allows custom URL patterns per content type. Match them to your WordPress permalink structure if SEO continuity matters. The import does not auto-generate redirects for changed URLs. You configure those manually under the Redirects section.<\/li>\n<\/ol>\n<ol>\n<li><strong>Front-end rendering.<\/strong> The default Astro theme renders basic blog and page layouts. For custom content types, you write Astro components that read the structured fields and render them. A &#8220;project&#8221; type with a client name, description, and gallery needs a <code>Project.astro<\/code>codecodecode component that pulls <code>data.clientName<\/code>codecodecode, <code>data.description<\/code>codecodecode, and <code>data.gallery<\/code>codecodecode and outputs them in the desired layout.<\/li>\n<\/ol>\n<h2>The Migration Is a Restructuring<\/h2>\n<p>The import tool moves the bytes. It does not move the structure. That is the counterintuitive truth that changes how you plan this work. A WordPress site with 15 custom post types built over 4 years is not a database export problem. It is a schema translation project. Every field that ACF stored as serialized PHP or HTML must be re-expressed as typed JSON.<\/p>\n<p>EmDash&#8217;s content type system is more powerful than WordPress&#8217;s post type + ACF combination. But that power comes from structure, not from flexibility. You trade the anything-goes meta table for a schema that the editor, the API, and the front end all understand equally. That trade is worth making. Just do not underestimate the work of making it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can export every post, page, and custom field from WordPress in about 90 seconds. The WXR file lands in your downloads folder. EmDash&#8217;s import tool accepts it. The data moves. That is not the problem. The problem is that WordPress stores content as HTML strings in the database, while EmDash uses portable text \u2014 [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":99252,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98019.png","fifu_image_alt":"The Counterintuitive Truth About Moving Custom Post Types From WordPress to EmDash","footnotes":""},"categories":[31],"tags":[],"class_list":["post-98019","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98019.png","fifu_image_alt":"The Counterintuitive Truth About Moving Custom Post Types From WordPress to EmDash","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98019","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/comments?post=98019"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98019\/revisions"}],"predecessor-version":[{"id":99253,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98019\/revisions\/99253"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/99252"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=98019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=98019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=98019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}