Strapi → Contentful
Export Strapi with strapi export, transform the JSONL entities into Contentful types, then load the space with the Contentful CLI. strapi export is built for another Strapi instance. Admin users and API tokens do not come along. Rebuild preview, roles, and webhooks.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
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
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
Contentful: Mature structured content APIs, localization, roles, and ecosystem support enterprise publishing. This removes a major source-side concern: Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS.
What you lose: An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive. What you inherit: Content-model complexity, API usage, and pricing can create significant platform dependence.
Jump to a section
Know the shape of the move.
This timeline assumes
- Strapi 5. You can run strapi export --no-encrypt --no-compress, or --format dir --no-encrypt on 5.42 or later.
- You have a Contentful space where you can create types and run contentful-import.
- Strapi stays online until the frontend no longer loads Strapi media URLs.
- The site already documents Contentful → Strapi and Strapi → Sanity as nearby routes.
- strapi transfer only talks to another Strapi /admin endpoint. It cannot load Contentful.
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 |
|---|---|---|---|---|
| Export archive | partial | critical | strapi export writes metadata, configuration, entities, links, schemas, and assets. Default output is encrypted and gzipped. Admin users and API tokens are not exported. | Export with --no-encrypt --no-compress or --format dir so you can read JSONL. Keep a separate admin user list. |
| Content types | manual | critical | Strapi schemas do not become Contentful content types. Components and dynamic zones need a Contentful model you design. | Create types first. Map __component values to Contentful content types or embedded entries. |
| Entries | partial | critical | Each entities_*.jsonl line is one record. You must transform it into Contentful entry JSON and preserve a stable id, preferably Strapi documentId. | Write a repeatable script. Use create-or-replace semantics so reruns converge. |
| Relations | partial | critical | Relations live in links/*.jsonl as well as on entities. A single pass can point at entries that do not exist yet. | Import entries, then patch links. Or import in dependency order. |
| Assets | partial | critical | Exported files come from the uploads folder. --only content still keeps asset database rows and can leave broken links. --exclude files does the same from the other side. | Export files and content together unless you have another store. Upload to Contentful. Replace media URLs. |
| Rich text and blocks | partial | high | Strapi Blocks, Markdown, HTML, or CKEditor JSON is not Contentful rich text. | Convert a sample per type. Keep the raw JSONL. |
| i18n | manual | critical | Strapi locale variants must become Contentful locale fields or separate entries. | Create locales in the space first. Import each locale on purpose. |
| Draft versus published | partial | high | Strapi draft and published rows can both appear. Contentful has its own draft API. | Map status. Do not publish drafts. |
| Roles, tokens, and admin users | lost | critical | Strapi documents that admin users and API tokens are omitted from export. | Recreate Contentful roles and tokens. Rotate anything that was in env files. |
| Webhooks and preview | manual | high | Strapi webhooks and preview URLs do not import. | Rebuild them in Contentful. |
| Frontend queries | manual | critical | REST, GraphQL, and documentId assumptions change. | Point a branch at Contentful Delivery or GraphQL before cutover. |
| Scheduled publishes | lost | medium | Strapi schedules do not become Contentful scheduled actions. | Recreate future publishes in Contentful. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Collection type | Contentful content type | manual | Create the type first. |
| documentId | Entry id or a stored field | transform | Keep a stable key. |
| entities JSONL | Contentful entry | transform | One line per record. |
| links JSONL | Entry links | transform | Apply after both sides exist. |
| uploads file | Contentful asset | transform | Upload, then link. |
| Component or dynamic zone | Embedded entry or rich text | transform | Map every __component. |
| Locale | Contentful locale | manual | Create locales first. |
| Draft flag | Draft or published entry | transform | Do not publish drafts. |
| Webhook | Contentful webhook | manual | New secrets. |
| Admin user or API token | No destination | unsupported | Not in the export. |
Make the move recoverable.
Create the source-of-truth backup
Keep a readable Strapi export and a running Strapi you can point at again.
- Run strapi export --no-encrypt --no-compress or --format dir --no-encrypt.
- Record type and entry counts per locale.
- Hash the archive or directory.
- List webhooks and preview URLs.
- Keep a Strapi backup you have restore-tested.
Proof to capture: Export hash, counts, and a named restore owner.
JSONL to Contentful JSON
Turn Strapi entities and links into a contentful-import payload.
- Read schemas first.
- Create missing content types.
- Transform entities.
- Upload assets.
- Patch links.
- Rerun until counts settle.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Frontend remap
Stop shipping Strapi URLs to production.
- Change space and token env vars on a branch.
- Map slugs and ids.
- Run the suite against Contentful.
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.
You import an encrypted archive you cannot read
The default .tar.gz.enc needs a key you did not store.
- Consequence
- The transform never starts.
- Mitigation
- Export again with --no-encrypt. Store the key if you keep encryption.
Stop if: You cannot open entities JSONL.
Asset rows without files
You used --only content or --exclude files.
- Consequence
- Contentful entries point at missing media.
- Mitigation
- Export files with content. Fail on missing binaries.
Stop if: A required asset has no file.
Frontend still reads Strapi
A production build still has STRAPI_URL.
- Consequence
- Editors change Contentful. The site does not.
- Mitigation
- Search env and CI. Watch production network.
Stop if: A production request still hits Strapi.
Do the work in this order.
- Days 1–5 · inventory
Inventory Strapi
12–20 hours active4–5 days elapsedOwner review waiting- List types, components, locales, and media providers.
- Decide Contentful type shapes.
Depends on: Strapi and Contentful admin access
Stop / go checkpointExport?
Go when: The type map is signed.
Stop when: A required dynamic zone has no target type.
- Days 5–8 · backup
Readable export
6–12 hours active2–4 days elapsedExport runtime waiting- Run a readable export.
- Hash files.
- Count entities and assets.
Depends on: inventory
Stop / go checkpointTransform?
Go when: JSONL opens and counts exist.
Stop when: The archive is encrypted without a key.
- Days 8–28 · pilot
Space and rehearsal import
40–100 hours active12–20 days elapsedImport runtime waiting- Create Contentful types.
- Transform and import a sample.
- Upload assets.
- Point a frontend branch at Contentful.
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 Strapi publishes.
- Re-export and rerun the import.
- Point production at Contentful.
Depends on: pilot
Stop / go checkpointContentful is the CMS?
Go when: Production reads Contentful only.
Stop when: A production page still uses Strapi.
- Days 35–40 · observe
Observe and close
6–10 hours active5–7 days elapsedPublish cycle waiting- Watch previews and asset hosts.
- Keep Strapi running.
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 Contentful the only CMS the production frontend reads.
- Freeze Strapi publishing.
- Run a final readable export and import.
- Reconcile counts and asset hosts.
- Switch production environment variables.
- Publish one known entry and confirm the live page.
Proof to capture: Production reads Contentful. Counts match. Assets do not use Strapi URLs you will cancel.
Rollback
Return the frontend to Strapi without losing Contentful-era edits you still want.
- Point production back at Strapi.
- Export Contentful entries changed after cutover.
- Apply only reviewed entries back to Strapi if needed.
- Keep the Strapi export.
Proof to capture: Production reads Strapi 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 | Readable archive | Open entities and links JSONL. | Files are not encrypted. Admin users are absent as documented. | Archive listing. | |
V-02Blocking | Entry counts | Compare Strapi and Contentful per type and locale. | Counts match or variances are signed. | Count sheet. | |
V-03Blocking | Files uploaded | Open a sample of media fields. | Required assets exist in Contentful. | Asset report. | |
V-04Blocking | Links | Open entries with several relations. | Links resolve. | Relation sample. | |
V-05Blocking | Production API | Watch network on a production build. | No Strapi API requests. | Network log. | |
V-06Blocking | Draft versus published | Publish one draft and unpublish one entry. | The frontend follows Contentful status. | Publish log. | |
V-07Blocking | Locale pages | Open each published locale for a sample type. | Approved locale content. | Locale sample. | |
V-08Blocking | Strapi still serves | Keep the project and a rollback env. | You can point the frontend back. | Rollback note. |
Remove the scaffolding safely.
Safe after: One publish cycle, no required Strapi media hosts, and seven stable days.
- Archive the export and importer log.
- Revoke Strapi tokens the frontend used.
- Keep Strapi running through the retention window.
- Rotate Contentful tokens created for the import.
- Document space backup.
- Schedule the next schema and token review.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Strapi: data exportAccessed 2026-10-31
- Contentful: CLI import and exportAccessed 2026-10-31
- Contentful: data export helpAccessed 2026-10-31
- Strapi: CLI transfer and export optionsAccessed 2026-10-31