Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
257 lines
9.7 KiB
Plaintext
257 lines
9.7 KiB
Plaintext
---
|
|
title: Migrate from WordPress
|
|
description: Import your WordPress content into EmDash with a step-by-step guide.
|
|
---
|
|
|
|
import { Aside, Card, CardGrid, Steps, Tabs, TabItem } from "@astrojs/starlight/components";
|
|
|
|
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
|
|
|
|
<CardGrid>
|
|
<Card title="Export your content" icon="document">
|
|
In WordPress, go to **Tools → Export** and download a complete export file (.xml).
|
|
</Card>
|
|
<Card title="Back up your site" icon="warning">
|
|
Keep your WordPress site running until you verify the migration succeeded.
|
|
</Card>
|
|
</CardGrid>
|
|
|
|
## 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
|
|
|
|
<Steps>
|
|
|
|
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
|
|
|
|
</Steps>
|
|
|
|
<Aside type="tip">
|
|
Re-running the import is safe. Items are matched by WordPress ID, so you won't create duplicates.
|
|
</Aside>
|
|
|
|
## Content Conversion
|
|
|
|
### Gutenberg to Portable Text
|
|
|
|
EmDash converts Gutenberg blocks to [Portable Text](https://github.com/portabletext/portabletext), 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:
|
|
|
|
<Steps>
|
|
|
|
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`
|
|
|
|
</Steps>
|
|
|
|
<Aside>
|
|
ACF repeater fields and flexible content import as JSON. Create matching Portable Text or array
|
|
fields in EmDash to structure this data.
|
|
</Aside>
|
|
|
|
## URL Redirects
|
|
|
|
After import, EmDash generates a redirect map:
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
## 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:
|
|
|
|
1. Export post types separately in WordPress
|
|
2. Import each file sequentially
|
|
3. Use the CLI with `--resume` for reliability
|
|
|
|
## Next Steps
|
|
|
|
- **[Content Import](/migration/content-import/)** — Other import sources and methods
|
|
- **[Plugin Porting](/migration/plugin-porting/)** — Migrate WordPress plugin functionality
|
|
- **[Working with Content](/guides/working-with-content/)** — Query and render your imported content
|