Skip to content

Migrate from WordPress

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:

MethodBest forIncludes draftsRequires auth
WXR file uploadComplete migrationsYesNo
WordPress.comWordPress.com hosted sitesYesOAuth
REST API (probe)Checking content before exportNoOptional

The WXR file upload is recommended for most migrations. It captures all content, including drafts, custom fields, and private posts.

  1. Export from WordPress

    In your WordPress admin, go to Tools → Export → All content → Download Export File.

  2. Open the Import wizard

    In EmDash, go to Admin → Settings → Import → WordPress.

  3. Upload your export file

    Drag and drop your .xml file or click to browse. The file is parsed in your browser.

  4. 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
  5. 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
  6. Execute the import

    Click Import Content. Progress displays as each item is processed.

  7. 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

EmDash converts Gutenberg blocks to Portable Text, a structured content format.

Gutenberg BlockPortable TextNotes
core/paragraphblock style=“normal”Inline marks preserved
core/headingblock style=“h1-h6”Level from block attributes
core/imageimage blockMedia reference updated
core/listblock with listItem typeOrdered and unordered
core/quoteblock style=“blockquote”Citation included
core/codecode blockLanguage attribute preserved
core/embedembed blockURL and provider stored
core/gallerygallery blockArray of image references
core/columnscolumns blockNested content preserved
Unknown blockshtmlBlockRaw 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 StatusEmDash Status
publishpublished
draftdraft
pendingpending
privateprivate
futurescheduled
trasharchived

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

WordPress post meta and ACF fields are analyzed during import:

  1. 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)
  2. Field mapping

    Internal WordPress fields (starting with _edit_, _wp_) are hidden by default. SEO plugin fields map to an seo object.

  3. 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

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 redirects option in astro.config.mjs

Use this table when adapting WordPress patterns to EmDash:

WordPressEmDashNotes
register_post_type()Collection in admin UICreated via dashboard or API
register_taxonomy()Taxonomy or array fieldDepends on complexity
register_meta()Field in collection schemaTyped, not key-value
WP_QuerygetCollection(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 blockCustom component renderer
register_block_type()Portable Text custom blockSame as shortcodes
add_menu_page()Plugin admin pageUnder /_emdash/admin/
add_action/filter()Plugin hookshooks.content:beforeSave
wp_optionsctx.kvKey-value store
wp_postmetaCollection fieldsStructured, not key-value
$wpdbctx.storageDirect storage access
Categories/TagsTaxonomiesHierarchical support preserved

Developers can also import via the CLI:

Terminal window
# Analyze export file
npx emdash import wordpress export.xml --analyze
# Run import
npx emdash import wordpress export.xml --execute
# With media download
npx emdash import wordpress export.xml --execute --download-media

The 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:

  • Rename the EmDash field
  • Change the WordPress field mapping
  • Delete and recreate the collection

For exports over 100MB, consider:

  1. Export post types separately in WordPress
  2. Import each file sequentially
  3. Use the CLI with --resume for reliability