Export your content
In WordPress, go to Tools → Export and download a complete export file (.xml).
EmDash provides a complete migration path from WordPress. Import your posts, pages, media, and taxonomies through the admin dashboard—no CLI required.
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.
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.
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 .xml file 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 attachmentsConfigure mappings
Toggle which post types to import. EmDash automatically:
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:
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.
HTML from the Classic Editor is converted to Portable Text blocks. Inline styles (<strong>, <em>, <a>) become marks on spans.
| WordPress Status | EmDash Status |
|---|---|
publish | published |
draft | draft |
pending | pending |
private | private |
future | scheduled |
trash | archived |
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/breakingWordPress 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 an seo object.
Type inference
EmDash infers field types from values:
number"1", "0", "true", "false" → booleandatejson_thumbnail_id) → referenceAfter 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:
redirects option in astro.config.mjsUse 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 |
Developers can also import via the CLI:
# Analyze export filenpx emdash import wordpress export.xml --analyze
# Run importnpx emdash import wordpress export.xml --execute
# With media downloadnpx emdash import wordpress export.xml --execute --download-mediaThe CLI uses the same APIs as the dashboard and supports --resume for interrupted imports.
The export file may be corrupted or incomplete. Re-export from WordPress.
Some images may be behind authentication or have moved. The import continues, and failed URLs are logged for manual handling.
If an existing collection has a field with an incompatible type, the import wizard shows the conflict. Either:
For exports over 100MB, consider:
--resume for reliability