How to Migrate from Contentful to EmDash CMS

Worth saying plainly upfront: unlike WordPress, EmDash doesn't ship a built-in, guided Contentful import wizard today — its documented import sources are WordPress-specific (WXR file, WordPress.com OAuth, and a WordPress REST API probe). Migrating from Contentful is a scripted process using EmDash's own REST API and CLI, not a one-click wizard. This walks through the real, working path.
Table of Contents
- Step 1: Export Your Contentful Space
- Step 2: Map Contentful Content Types to EmDash Collections
- Step 3: Handle Assets
- Step 4: Convert Rich Text to Portable Text
- Step 5: Script the Import via CLI or REST API
- Step 6: For Recurring Migrations, Build a Custom Import Source
- Frequently Asked Questions
- Is there really no one-click Contentful migration?
- What's the hardest part of this migration in practice?
- Can I keep Contentful running during the migration?
- Should I migrate everything at once or in batches?
- The Bottom Line
Step 1: Export Your Contentful Space
Contentful publishes its own official export tool for exactly this purpose. Install and run it against your space:
npm install -g contentful-export
contentful-export --space-id YOUR_SPACE_ID --management-token YOUR_TOKEN --export-dir ./contentful-exportThis produces a JSON file containing your content types, entries, assets, and locales — the full shape of your Contentful space, structured and ready to map into EmDash's schema.
Step 2: Map Contentful Content Types to EmDash Collections
Contentful's content-type fields map reasonably cleanly onto EmDash's 16 field types. The mapping is mostly one-to-one, with rich text and references needing the most attention:
- Contentful Text / Symbol → EmDash string or text
- Contentful Rich Text → EmDash portableText (structural conversion required, see Step 4)
- Contentful Number / Integer → EmDash number or integer
- Contentful Boolean → EmDash boolean
- Contentful Date → EmDash datetime
- Contentful Media (single) → EmDash image or file
- Contentful Reference (single) → EmDash reference
- Contentful Reference (many) / Array → EmDash reference with allowMultiple, or repeater
Create the matching collections and fields in EmDash's Visual Schema Builder (admin panel, no code required for the schema itself), or script it directly with the CLI's schema commands if you're mapping many content types and want it reproducible.
Step 3: Handle Assets
Contentful's exported asset URLs point to Contentful's own CDN. Download each asset from its exported URL and upload it into EmDash's media library through the admin UI — EmDash's media library supports drag-and-drop uploads with signed URL support, and once uploaded, each asset gets an EmDash media ID you'll reference when creating entries in Step 5.
Step 4: Convert Rich Text to Portable Text
This is the step with no shortcut. Contentful's Rich Text field is also a block-based, structured format — conceptually close to Portable Text, but a different spec with different type names and structure. A direct field-by-field JSON transform (mapping Contentful's node types like `paragraph`, `heading-1`, `unordered-list`, and `hyperlink` to EmDash's `block` style variants and mark definitions) is the practical approach. For a small number of entries, this can be done by hand; for a large content library, write a small conversion script that walks the Contentful Rich Text document tree and emits EmDash's Portable Text block shape.
Portable Text is a specification for structured rich text. The value is stored as a JSON array of Portable Text blocks — each block has a _type, style, and children array of typed spans.
Step 5: Script the Import via CLI or REST API
With content types mapped and assets uploaded, create entries programmatically. The EmDash CLI's content commands work well for a scripted migration:
npx emdash content create posts --file entry-001.json --slug my-first-post
# Or pipe data directly:
cat entry-001.json | npx emdash content create posts --stdinFor a larger migration, the REST API is more practical to drive from a script that loops over your exported Contentful entries:
POST /_emdash/api/content/posts
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
"title": "My First Post",
"slug": "my-first-post",
"content": [ /* converted Portable Text blocks */ ],
"status": "draft"
}Items are created as drafts by default unless you explicitly set `status: "published"` — a deliberate safety net, letting you review converted content in the EmDash admin before anything goes live, especially useful given the Rich Text conversion in Step 4 is the part most likely to need manual touch-ups.
Step 6: For Recurring Migrations, Build a Custom Import Source
If you're doing this migration repeatedly — an agency migrating multiple clients off Contentful, for instance — EmDash exposes a documented `ImportSource` interface (`probe`, `analyze`, `fetchContent`) you can implement and register on the integration, giving Contentful the same guided-wizard experience WordPress gets today, reusable across every future migration instead of a one-off script.
// astro.config.mjs
import { contentfulSource } from "./src/import/contentful-source";
export default defineConfig({
integrations: [
emdash({
import: { sources: [contentfulSource] },
}),
],
});Frequently Asked Questions
Is there really no one-click Contentful migration?
Not today — EmDash's built-in, wizard-guided import sources are WordPress-specific. A Contentful migration is a real, working process using documented tools (EmDash's REST API, CLI, and Custom Sources interface), just not a single guided flow the way WordPress import is.
What's the hardest part of this migration in practice?
Converting Contentful's Rich Text fields to Portable Text — both are block-based and structurally similar, but the actual JSON shapes differ enough that a naive copy-paste won't work. Budget real time for this step specifically, especially for content with complex embedded entries or heavy inline formatting.
Can I keep Contentful running during the migration?
Yes — the export step is read-only against your Contentful space, so there's no reason to take it offline while you build and test the migration script against EmDash.
Should I migrate everything at once or in batches?
For anything beyond a small content library, batch by content type and verify each batch in EmDash's admin before moving to the next — that isolates a Rich Text conversion bug to one batch instead of discovering it after importing everything.
The Bottom Line
Migrating from Contentful to EmDash today is a real, scripted process — Contentful's own export tool, a field-type mapping pass, a Rich-Text-to-Portable-Text conversion, and either CLI or REST API calls to create the entries — not a guided wizard the way WordPress migration is. For teams doing this repeatedly, building a proper `ImportSource` turns that one-off script into a reusable, guided flow. See our 10-minute EmDash setup guide to get a project running before you start mapping content types.




Comments