Drupal → Contentful
Read Drupal content through JSON:API, reshape it for a Contentful content model, and load it with the Content Management API or the CLI. Contentful's own migration article says referenced entries have to exist first, and HTML has to become rich text.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Drupal
- Content types, fields, and a deep module ecosystem fit complex publishing sites
- Editors get a purpose-built publishing workflow instead of raw files
- Views, custom fields, and module data need an explicit map before they mean anything in another 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: Views, custom fields, and module data need an explicit map before they mean anything in another CMS.
What you lose: Content types, fields, and a deep module ecosystem fit complex publishing sites. 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
- JSON:API is enabled on a Drupal environment you are allowed to read. The module page is the core HTTP API for entities.
- You have a Contentful space and a management token stored outside the repo.
- The content model is already created. Contentful's article says the import follows the model, it does not invent one from Drupal fields.
- You will transform HTML with Contentful's HTML-to-rich-text library, which their article says they maintain.
- Drupal stays up until assets in Contentful match the files you listed.
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 model | manual | critical | Contentful entries follow content types you define. Drupal content types and fields do not become those types by exporting JSON. | Design the types first. Write a map from each Drupal field to a Contentful field. |
| Entities | partial | critical | JSON:API can read entities the module exposes. A script still has to create entries. The CLI import expects Contentful's own export shape, not raw Drupal JSON. | Transform into the CLI or CMA shape. Do not post Drupal JSON at the CLI and hope. |
| References | partial | critical | Contentful's article says you create referenced entries first. Their example is authors before the posts that point at them. | Order the load. Fail the run if a link points at an id you have not created. |
| HTML bodies | partial | critical | The article says Drupal HTML should become Contentful rich text, using their conversion library, because rich text is JSON rather than an HTML blob. | Convert a sample of each text format. Read the result in the Contentful editor. |
| Files | partial | critical | Files have to be uploaded as Contentful assets and then linked. A JSON:API file URL is not an asset id. | Upload assets first. Store the Drupal file id next to the asset id. |
| Users and permissions | manual | high | Drupal accounts do not become Contentful users. The article's path is a content migration, not an identity migration. | Invite the editors you still need. Keep bylines as entries or as text, on purpose. |
| Views, blocks, and theme | manual | critical | Presentation stays in your frontend. Contentful does not render a Drupal theme. | Rebuild the routes in the frontend you chose. This playbook stops at the content load. |
| Modules | manual | high | The article says to audit modules and find a Contentful app, or port behavior with the App Framework. That is a product decision, not an import. | List modules that are in use. Mark each as an app, custom code, or dropped. |
| Revisions and unpublished workflow | partial | high | Drupal revisions do not become Contentful snapshots unless you create entries in the states you want. Draft versus published is a Contentful step you run. | Decide which revision is the one you publish. Do not publish the space by accident. |
| Paths | manual | critical | Path aliases are your frontend's routes now. Contentful does not host the old Drupal URLs by itself. | Keep a redirect map on the host that still answers the domain. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Content type | Contentful content type | manual | Create it before the load. |
| Node id | A stored Drupal id field | transform | Use it to reconcile and to build links. |
| Text field | Symbol, text, or rich text | transform | HTML goes through the converter when the destination is rich text. |
| Entity reference | Entry link | transform | Create the target entry first. |
| File or image | Asset | transform | Upload, then link. |
| Taxonomy term | Entry or a short text list | manual | Pick one model and stick to it. |
| Author | Entry or text | manual | Not a Contentful user login. |
| Path alias | Frontend route and a redirect | manual | Owned by the site in front of Contentful. |
| View or block | No destination | unsupported | Rebuild in the frontend. |
Make the move recoverable.
Create the source-of-truth backup
Keep Drupal restorable while Contentful receives a copy.
- Back up the database, sites directory, uploads, and custom code, as the user guide describes.
- Save a JSON:API extract of the types you will migrate.
- Export the empty Contentful content model with the CLI before the first import, so you can recreate the space shape.
- Record the management token's owner and its expiry.
Proof to capture: Drupal restores from the backup, and you have a CLI export of the content model from before entries were added.
Field map and rich text
Turn Drupal entities into Contentful entries without losing links.
- Read one content type from JSON:API.
- Convert HTML fields and record any node the library skips.
- Write entries only after their targets exist.
- Store the Drupal id on the entry.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
CLI load
Upload a transformed space export, or create entries through the CMA if you script that instead.
- Import into a non-production space or environment first.
- Compare entry counts by content type.
- Publish only the entries that passed the sample read.
- Keep the Drupal extract so you can rebuild the file.
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.
Links are created before their targets
The API rejects the entry, or the link is empty.
- Consequence
- A whole type fails late in the run.
- Mitigation
- Load targets first and fail on a missing id.
Stop if: Any required link is unresolved.
HTML is pasted into rich text
The field contains a string of HTML instead of a rich text document.
- Consequence
- Editors cannot use the field, or the frontend renders tags as text.
- Mitigation
- Run the converter and open the entry.
Stop if: A sample body is still raw HTML.
The production space is published early
Draft content or the wrong revision is public.
- Consequence
- Visitors see unfinished entries.
- Mitigation
- Use a separate environment and publish on purpose.
Stop if: An entry is public before the sample check.
Do the work in this order.
- Days 1 to 5 · inventory
Map types and fields
10 to 20 hours active3 to 5 days elapsedEditors and developers waiting- List types, references, and text formats.
- Create the Contentful model.
Depends on: Drupal read access, Contentful admin
Stop / go checkpointExtract?
Go when: Every field has a destination or a skip.
Stop when: A reference has no target type.
- Days 5 to 7 · backup
Back up Drupal and the model
4 to 8 hours active1 to 2 days elapsedDatabase dump waiting- Back up Drupal.
- Export the Contentful model with the CLI.
Depends on: Approved model
Stop / go checkpointTransform a type?
Go when: Both backups restore or re-import as an empty model.
Stop when: The model export fails.
- Days 7 to 18 · pilot
Load one type
16 to 40 hours active5 to 12 days elapsedAsset uploads waiting- Convert one type.
- Create targets first.
- Read entries in the web app.
Depends on: Verified backup
Stop / go checkpointLoad the other types?
Go when: Counts match and rich text opens cleanly.
Stop when: Links or HTML fail.
- Days 18 to 25 · cutover
Load the rest and publish
10 to 24 hours active3 to 7 days elapsedFrontend deploy waiting- Freeze Drupal edits.
- Load the final delta.
- Publish the environment you checked.
Depends on: Passed pilot
Stop / go checkpointPublish?
Go when: Counts match and the frontend reads the space.
Stop when: Drupal is still changing, or a type is short.
- Days 25 to 40 · observe
Watch the frontend
8 to 20 hours active5 to 15 days elapsedA publishing cycle waiting- Editors change one entry in Contentful.
- Keep the Drupal backup.
Depends on: Verified cutover
Stop / go checkpointStop Drupal writes for good?
Go when: Editors can publish, and the checks still pass.
Stop when: An asset or a route fails.
Cut over with a way back.
Cutover
Publish the checked Contentful environment and stop Drupal edits.
- Freeze Drupal.
- Re-read changed nodes and upsert them by Drupal id.
- Compare counts and a sample of rich text and assets.
- Publish that environment.
- Point the frontend at it and keep redirects on the old host.
Proof to capture: The frontend reads Contentful, entry counts match, and Drupal is unchanged since the freeze.
Rollback
Serve Drupal again.
- Point the frontend or DNS back at Drupal.
- Unpublish the Contentful environment if it went out by mistake.
- Export entries created only in Contentful.
- Review them before anyone retypes them into Drupal.
- Confirm the old URLs respond.
Proof to capture: Drupal is live again, and Contentful-only edits are listed.
- A content type count is short
- A required asset 404s
- Rich text is still HTML
- A required link is empty
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Field map | Read the content type in Contentful against the map. | Every required field exists. | Model diff | |
V-02Blocking | Entries | Compare Drupal entity counts with Contentful entries. | Scoped types match. | Count sheet | |
V-03Blocking | References | Open an entry with links. | Targets exist and are the right ids. | Link sample | |
V-04Blocking | Bodies | Open a converted body in the editor and on the frontend. | It is rich text, and the sample matches Drupal. | Body sample | |
V-05Blocking | Files | Open a sample of assets anonymously. | They match the Drupal files. | Asset sample | |
V-06Blocking | Drupal id | Fetch an entry by the stored Drupal id. | It is unique. | Id sample | |
V-07Blocking | State | Check draft versus published on a sample. | Only the intended entries are public. | State sample | |
V-08Blocking | Old URLs | Request the old Drupal paths on the host that still owns the domain. | Redirects land on the new routes. | Redirect log |
Remove the scaffolding safely.
Safe after: Editors have published in Contentful for a cycle, and Drupal still restores from backup.
- Archive the JSON:API extract and the CLI model export.
- Revoke the management token you used for the load.
- Remove staging spaces you do not need.
- Keep Drupal until redirects and rollback are done.
- Note the library version you used for HTML conversion.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Drupal: JSON:API moduleAccessed 2026-11-05
- Contentful: CLI import and exportAccessed 2026-11-05
- Contentful: Drupal migration articleAccessed 2026-11-05
- Drupal User Guide: data backupsAccessed 2026-11-05