Contentful → Strapi
Export Contentful with the CLI, recreate content types in Strapi, then load entries and assets with a script. Keep field names close and store the Contentful id on every type so you can rerun the import. Rich text, locales, and roles do not arrive as a one-click switch.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Contentful
- Mature structured content APIs, localization, roles, and ecosystem support enterprise publishing
- Editors get a purpose-built publishing workflow instead of raw files
- Content-model complexity, API usage, and pricing can create significant platform dependence
- Themes, extensions, and content models add maintenance and portability constraints
Strapi
- An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive
- Editors get a purpose-built publishing workflow instead of raw files
- Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS
- Themes, extensions, and content models add maintenance and portability constraints
Strapi: An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive. This removes a major source-side concern: Content-model complexity, API usage, and pricing can create significant platform dependence.
What you lose: Mature structured content APIs, localization, roles, and ecosystem support enterprise publishing. What you inherit: Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS.
Jump to a section
Know the shape of the move.
This timeline assumes
- Strapi 5, self-hosted or Strapi Cloud, with admin access to create types and a transfer or API token.
- You can run contentful-export and download assets. The strapi_lift writeup moved about 2,000 entries and 11 GB of assets in about 7 hours.
- Contentful stays readable until Strapi no longer loads Contentful asset URLs.
- The site already documents Contentful → Sanity, Contentful → WordPress, and Contentful → Astro if you want those destinations.
- strapi export is for Strapi-to-Strapi. It is not the Contentful importer. Admin users and API tokens are not included in a Strapi export.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Content types | manual | critical | There is no official Contentful-to-Strapi type importer. The 2025 guide says to recreate types and keep field names close. Every type needs a contentful_id field if you use strapi_lift or a similar id map. | Model types first. Add contentful_id. Do not import until the schema matches. |
| Entries | partial | critical | contentful-export writes JSON. A custom script or strapi_lift creates or updates Strapi documents. strapi_lift resolves relations one level deep and wants a second pass so links land. | Run the import twice. Reconcile accepted, updated, and rejected ids. |
| Assets | partial | critical | The guide warns that a Contentful export can include 0-byte files when a download failed. Assets used in rich text must be uploaded and then linked. | Check file sizes before import. Re-download failures. Do not leave production HTML on Contentful CDN URLs. |
| Rich text | partial | high | Contentful rich text is a document graph. Strapi may use Blocks, Markdown, or another editor. That is a transform, not a copy. | Convert a sample of every content type that has rich text. Keep the raw export. |
| Locales | manual | critical | Contentful locales and fallbacks do not become Strapi i18n by themselves. | Enable i18n on the types that need it. Import each locale on purpose. |
| Roles, workflows, and comments | lost | high | Contentful roles, tasks, comments, and workflow state are not Strapi admin users. Strapi export also skips admin users and API tokens. | Recreate roles in Strapi. Archive Contentful comments if you still need them. |
| Preview, apps, and webhooks | manual | high | Contentful apps, preview URLs, and webhook signing secrets stay behind. | Rebuild preview and webhooks against Strapi. |
| Identifiers | partial | critical | Johny W. Alves described a Contentful-to-Strapi 5 move at TCGRP where documentId versus numeric id broke legacy Server Components. Treat that LinkedIn note as one team’s report. | Store Contentful ids. Decide whether public URLs use documentId, slug, or a new id. |
| GraphQL and frontend queries | manual | critical | The hochzeitsplaza writeup wanted GraphQL. Field names can stay, but queries, locales, and asset URLs still change. | Point a branch of the frontend at Strapi before cutover. Run the test suite. |
| Scheduled actions and releases | lost | medium | Contentful scheduled actions and releases do not import. | Export the schedule. Recreate future publishes in Strapi. |
| Draft versus published | partial | high | Drafts and published locales can flatten if the script only reads the published API. | Export preview and published. Map status explicitly. |
| Hosting and operations | manual | high | You now run Strapi, a database, and asset storage. Contentful’s SLA does not follow you. | Rehearse backups and a restore before you switch the frontend. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Content type | Strapi collection or single type | manual | Keep field names close. |
| sys.id | contentful_id | transform | Required for safe reruns. |
| Entry fields | Strapi attributes | transform | Watch required fields and types. |
| Link to entry | Relation | transform | Resolve after both sides exist. |
| Link to asset | Media field | transform | Upload first. |
| Rich text document | Blocks, Markdown, or custom JSON | transform | Convert a sample per type. |
| Locale | Strapi i18n locale | manual | Enable i18n first. |
| Slug | UID | transform | Keep public slugs. |
| Webhook | Strapi webhook | manual | New secrets. |
| Role or workflow | No destination | unsupported | Recreate in Strapi admin. |
Make the move recoverable.
Create the source-of-truth backup
Keep a Contentful export and asset folder you can reimport or fall back to.
- Run contentful-export for the space, including assets.
- Record content type and entry counts per locale.
- Hash the JSON and the asset folder.
- Export webhook URLs and preview settings.
- Keep Contentful preview working.
Proof to capture: Export hash, entry counts, and a named restore owner.
Schema and id map
Give the importer a Strapi type that can find Contentful rows again.
- Recreate each type.
- Match field names where you can.
- Add contentful_id.
- Enable i18n only on types that need it.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Entry and asset load
Create or update documents without duplicating on rerun.
- Upload assets and skip 0-byte files.
- Create entries with field values.
- Resolve relations on a second pass.
- Compare Contentful ids to Strapi documents.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Missing contentful_id
The importer cannot match an existing Strapi row to the export.
- Consequence
- Reruns duplicate entries or skip updates.
- Mitigation
- Add the field on every imported type before the first run.
Stop if: A type in scope has no contentful_id.
0-byte assets
The Contentful export folder has empty files.
- Consequence
- Production pages show broken images.
- Mitigation
- Fail the import on 0-byte files. Re-download.
Stop if: A required asset is empty after one retry.
Frontend still queries Contentful
A preview or production build still has CONTENTFUL_SPACE.
- Consequence
- Editors change Strapi. The site does not.
- Mitigation
- Search env files and CI. Run the production build against Strapi first.
Stop if: A production request still hits Contentful Delivery API.
Do the work in this order.
- Days 1–5 · inventory
Inventory Contentful
12–20 hours active4–5 days elapsedOwner review waiting- List types, locales, apps, and webhooks.
- Decide public ids and slugs.
Depends on: Contentful and Strapi admin access
Stop / go checkpointExport?
Go when: The type map is signed.
Stop when: A required locale or type is unknown.
- Days 5–8 · backup
Export the space
6–12 hours active2–4 days elapsedAsset download waiting- Run contentful-export.
- Hash files.
- Count 0-byte assets.
Depends on: inventory
Stop / go checkpointBuild types?
Go when: The export hash and counts exist.
Stop when: Assets failed at scale.
- Days 8–28 · pilot
Schema and rehearsal import
40–100 hours active12–20 days elapsedImport runtime waiting- Create Strapi types.
- Convert rich text samples.
- Run the importer.
- Point a frontend branch at Strapi.
Depends on: backup
Stop / go checkpointCut over?
Go when: Counts, assets, and the frontend pass.
Stop when: A required relation or locale is wrong.
- Days 28–35 · cutover
Freeze and switch the API
8–18 hours active3–5 days elapsedFinal export waiting- Freeze Contentful publishes.
- Re-export and rerun the importer.
- Point production at Strapi.
Depends on: pilot
Stop / go checkpointStrapi is the CMS?
Go when: Production reads Strapi only.
Stop when: A production page still uses Contentful.
- Days 35–40 · observe
Observe and close
6–10 hours active5–7 days elapsedPublish cycle waiting- Watch previews, webhooks, and asset hosts.
- Keep Contentful readable.
Depends on: cutover
Stop / go checkpointClose rollback?
Go when: One publish cycle is clean.
Stop when: Ids or assets regress.
Cut over with a way back.
Cutover
Make Strapi the only CMS the production frontend reads.
- Freeze Contentful publishing.
- Run a final export and importer pass.
- Reconcile counts and asset hosts.
- Switch production environment variables.
- Publish one known entry and confirm the live page.
Proof to capture: Production reads Strapi. Counts match. Assets do not use Contentful URLs you will cancel.
Rollback
Return the frontend to Contentful without losing Strapi-era edits you still want.
- Point production back at Contentful.
- Export Strapi entries changed after cutover.
- Apply only reviewed entries back to Contentful if needed.
- Keep the Contentful export.
Proof to capture: Production reads Contentful again.
- Count mismatch
- Broken required asset
- Frontend still mixed between APIs
- Locale missing
- Preview or webhook failure
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | contentful_id present | Inspect each imported type. | Every in-scope type has the field populated. | Schema screenshot. | |
V-02Blocking | Entry counts | Compare Contentful and Strapi per type and locale. | Counts match or variances are signed. | Count sheet. | |
V-03Blocking | File sizes and hosts | Reject 0-byte files. Sample live pages. | Required assets are non-empty and not on a host you will cancel. | Asset report. | |
V-04Blocking | Second-pass links | Open entries that have several relations. | Links resolve. | Relation sample. | |
V-05Blocking | Production API | Search env and watch network on a production build. | No Contentful Delivery requests. | Network log. | |
V-06Blocking | Draft versus published | Publish one draft and unpublish one entry. | The frontend follows Strapi status. | Publish log. | |
V-07Blocking | Locale pages | Open each published locale for a sample type. | Approved locale content. | Locale sample. | |
V-08Blocking | Contentful still serves | Keep the space and a rollback env. | You can point the frontend back. | Rollback note. |
Remove the scaffolding safely.
Safe after: One publish cycle, no required Contentful asset hosts, and seven stable days.
- Archive the export, asset folder, and importer log.
- Remove contentful_id only after you know you will not rerun.
- Revoke Contentful tokens the frontend used.
- Keep the space readable through the retention window.
- Document Strapi backup and restore.
- Schedule the next schema and token review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Tobias Schlottke moved hochzeitsplaza.de off Contentful after a price increase. He wanted an open CMS with GraphQL and wrote strapi_lift because no stock importer existed. The tool reads a Contentful export, creates or updates Strapi documents using a contentful_id field on every type, uploads assets, and resolves relations one level deep so you can rerun the import. His project had about 2,000 entries and more than 11 GB of assets and took about 7 hours. He warns that Contentful exports can include 0-byte files, and that mismatched field names break the load. A matching article also appears on the Strapi blog. This is the author’s own site.
- Add a contentful_id field on every imported type before the first run so reruns update instead of duplicating.
- Inspect the asset folder for 0-byte files and keep field names close to the Contentful model.
- A custom importer plus a stable Contentful id is how they moved 2,000 entries, not a Strapi menu item.
- Relations needed a second pass because the tool only resolves one level at a time.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Strapi: migrate from Contentful (2025 guide)Accessed 2026-10-31
- Tobias Schlottke: automated Contentful to Strapi with strapi_liftAccessed 2026-10-31
- Contentful: CLI import and exportAccessed 2026-10-31
- Strapi: data exportAccessed 2026-10-31