EmDash provides a complete migration path from WordPress. Import your posts, pages, media, and taxonomies through the admin dashboard—no CLI required.
Before You Begin
Export your content
In WordPress, go to Tools → Export and download a complete export file (.xml).
Back up your site
Keep your WordPress site running until you verify the migration succeeded.
Import Methods
EmDash supports three methods for importing WordPress content:
| Method | Best for | Includes drafts | Requires auth |
|---|---|---|---|
| WXR file upload | Complete migrations | Yes | No |
| WordPress.com | WordPress.com hosted sites | Yes | OAuth |
| REST API (probe) | Checking content before export | No | Optional |
The WXR file upload is recommended for most migrations. It captures all content, including drafts, custom fields, and private posts.
WXR File Import
-
Export from WordPress
In your WordPress admin, go to Tools → Export → All content → Download Export File.
-
Open the Import wizard
In EmDash, go to Admin → Settings → Import → WordPress.
-
Upload your export file
Drag and drop your
.xmlfile or click to browse. The file is parsed in your browser. -
Review detected content
The wizard shows what was found:
Found in export: ├── Posts: 127 → posts [New collection] ├── Pages: 12 → pages [Add fields] └── Media: 89 attachments -
Configure mappings
Toggle which post types to import. EmDash automatically:
- Creates new collections for unmapped post types
- Adds missing fields to existing collections
- Warns about field type conflicts
-
Execute the import
Click Import Content. Progress displays as each item is processed.
-
Import media (optional)
After content imports, choose whether to download media files. EmDash:
- Downloads from your WordPress URLs
- Deduplicates by content hash
- Rewrites URLs in your content automatically
Content Conversion
Gutenberg to Portable Text
EmDash converts Gutenberg blocks to Portable Text, a structured content format.
| Gutenberg Block | Portable Text | Notes |
|---|---|---|
core/paragraph | block style=“normal” | Inline marks preserved |
core/heading | block style=“h1-h6” | Level from block attributes |
core/image | image block | Media reference updated |
core/list | block with listItem type | Ordered and unordered |
core/quote | block style=“blockquote” | Citation included |
core/code | code block | Language attribute preserved |
core/embed | embed block | URL and provider stored |
core/gallery | gallery block | Array of image references |
core/columns | columns block | Nested content preserved |
| Unknown blocks | htmlBlock | Raw HTML preserved for review |
Unknown blocks are stored as htmlBlock with the original HTML and block metadata. You can review and convert these manually or create custom Portable Text components to render them.
Classic Editor Content
HTML from the Classic Editor is converted to Portable Text blocks. Inline styles (<strong>, <em>, <a>) become marks on spans.
Status Mapping
| WordPress Status | EmDash Status |
|---|---|
publish | published |
draft | draft |
pending | pending |
private | private |
future | scheduled |
trash | archived |
Taxonomy Import
Categories and tags import as taxonomies with hierarchy preserved:
WordPress: EmDash:
├── Categories (hierarchical) ├── taxonomies table
│ ├── News │ ├── category/news
│ │ ├── Local │ ├── category/local (parent: news)
│ │ └── World │ ├── category/world (parent: news)
│ └── Sports │ └── category/sports
└── Tags (flat) └── content_taxonomies junction
├── featured ├── tag/featured
└── breaking └── tag/breaking
Custom Fields and ACF
WordPress post meta and ACF fields are analyzed during import:
-
Analysis phase
The wizard detects custom fields and suggests EmDash field types:
Custom Fields: ├── subtitle (string, 45 posts) ├── _yoast_wpseo_title → seo.title (string, 127 posts) ├── _thumbnail_id → featuredImage (reference, 89 posts) └── price (number, 23 posts) -
Field mapping
Internal WordPress fields (starting with
_edit_,_wp_) are hidden by default. SEO plugin fields map to anseoobject. -
Type inference
EmDash infers field types from values:
- Numeric strings →
number "1","0","true","false"→boolean- ISO dates →
date - Serialized PHP/JSON →
json - WordPress IDs (e.g.,
_thumbnail_id) →reference
- Numeric strings →
URL Redirects
After import, EmDash generates a redirect map:
{
"redirects": [
{ "from": "/?p=123", "to": "/posts/hello-world" },
{ "from": "/2024/01/hello-world/", "to": "/posts/hello-world" },
{ "from": "/category/news/", "to": "/categories/news" }
],
"feeds": [
{ "from": "/feed/", "to": "/rss.xml" },
{ "from": "/feed/atom/", "to": "/atom.xml" }
]
}
Apply these redirects to:
- Cloudflare redirect rules
- Your hosting platform’s redirect config
- Astro’s
redirectsoption inastro.config.mjs
Concept Mapping Reference
Use this table when adapting WordPress patterns to EmDash:
| WordPress | EmDash | Notes |
|---|---|---|
register_post_type() | Collection in admin UI | Created via dashboard or API |
register_taxonomy() | Taxonomy or array field | Depends on complexity |
register_meta() | Field in collection schema | Typed, not key-value |
WP_Query | getCollection(filters) | Runtime queries |
get_post() | getEntry(collection, id) | Returns entry or null |
wp_insert_post() | POST /_emdash/api/content/{type} | REST API |
the_content | <PortableText value={...} /> | Portable Text rendering |
add_shortcode() | Portable Text custom block | Custom component renderer |
register_block_type() | Portable Text custom block | Same as shortcodes |
add_menu_page() | Plugin admin page | Under /_emdash/admin/ |
add_action/filter() | Plugin hooks | hooks.content:beforeSave |
wp_options | ctx.kv | Key-value store |
wp_postmeta | Collection fields | Structured, not key-value |
$wpdb | ctx.storage | Direct storage access |
| Categories/Tags | Taxonomies | Hierarchical support preserved |
API Import (Advanced)
The WordPress import is available through the admin dashboard and the REST API. Use the admin dashboard import wizard for the best experience — it provides field mapping, conflict resolution, and progress tracking.
The import API endpoints are under /_emdash/api/import/wordpress/ for programmatic access.
Troubleshooting
”XML parsing error”
The export file may be corrupted or incomplete. Re-export from WordPress.
Media download failures
Some images may be behind authentication or have moved. The import continues, and failed URLs are logged for manual handling.
Field type conflicts
If an existing collection has a field with an incompatible type, the import wizard shows the conflict. Either:
- Rename the EmDash field
- Change the WordPress field mapping
- Delete and recreate the collection
Large exports
For exports over 100MB, consider:
- Export post types separately in WordPress
- Import each file sequentially
- Use the CLI with
--resumefor reliability
Next Steps
- Content Import — Other import sources and methods
- Plugin Porting — Migrate WordPress plugin functionality
- Working with Content — Query and render your imported content