Back to Blog

How Contentful's Content Model Creates Migration Complexity

June 29, 2026 6 min read
How Contentful's Content Model Creates Migration Complexity

Contentful is a well-designed platform. Its content model gives teams serious flexibility in how they structure, link, and deliver content across channels. That flexibility is one of the reasons so many teams chose it in the first place.

But that same flexibility is also what makes migrating away from Contentful technically challenging. The features that make Contentful powerful in production, links, rich text, localization, and modular content types, are the same features that create complexity during a migration.

This is not a criticism of Contentful. It is a technical walkthrough of the specific content model features that migration teams need to understand before they start moving data.

Links and references: the sys.id chain

Everything in Contentful is connected through links. An entry can reference another entry. That entry can reference another one. And so on. Contentful resolves these linked entries up to 10 levels deep through its Delivery API.

Under the hood, a reference field stores a sys.id pointer to another entry. When you fetch an entry through the REST API, the linked entries are not inline. They come back in a separate includes array. The Contentful SDK uses the contentful-resolve-response package to walk through the includes array and stitch the referenced entries back into the response tree.

Here is why this matters for migration:

When you export content from Contentful, you get entries with sys.id references pointing to other entries. But those references are just IDs. They do not carry the actual content. To reconstruct the full content tree, you need to resolve every reference chain, fetch the linked entries, and understand how deeply nested the relationships go.

On a small site with a flat content model, this is straightforward. On a production site with years of accumulated content, reference chains can go four or five levels deep. A page entry references a section entry, which references a component entry, which references an asset, which references a metadata entry. Each level adds complexity to the export and transformation process.

The real problem shows up when you try to map these references to a destination platform. Different CMS platforms handle references differently. Some use direct embedding. Some use reference fields with different resolution behavior. Some flatten what Contentful keeps nested. If you do not map the reference chain correctly, you end up with broken links, orphaned entries, or circular references that the destination platform cannot resolve.

A common scenario: a Contentful space has navigation entries that reference page entries, which reference section entries, which reference the same navigation entries through a footer component. That circular reference works fine in Contentful because the SDK handles resolution gracefully. But when you export the data for migration, you need to detect and break the circular chain before the destination platform chokes on it.

Rich Text: JSON AST, not HTML

Contentful's Rich Text field is one of its most powerful features. It is also one of the most complex to migrate.

Unlike traditional WYSIWYG editors that store content as HTML, Contentful's Rich Text field stores content as a JSON Abstract Syntax Tree. Every paragraph, heading, list item, and formatting mark is a node in the tree. That is the straightforward part.

The complex part is embedded entries and assets.

Contentful's Rich Text field allows authors to embed other entries and assets directly in the flow of text. An author can write a paragraph, embed a code block entry, add another paragraph, embed an image asset, and then embed a video entry. All of that lives inside a single Rich Text field as nodes in the AST.

When you fetch a Rich Text field through the REST API, the embedded entries come back as sys.id references inside the AST nodes. The actual entry data comes back in the includes array, just like regular reference fields. The SDK resolves them for you during delivery.

During migration, you have to do that resolution yourself. You need to walk the AST, find every embedded-entry-block, embedded-entry-inline, embedded-asset-block, and entry-hyperlink node, look up the referenced entry or asset, and decide how to represent it in the destination platform.

Every destination platform handles embedded content differently. Some use their own rich text AST format. Some use block-level components. Some use a different embedding model entirely. The transformation from Contentful's AST to the destination format is custom work for every migration.

And there is a subtlety that catches teams off guard. Contentful's updateReferences flag in migration scripts does not update references inside Rich Text fields. That is a documented limitation. If you are using Contentful's own migration CLI to restructure content types and you have embedded entries in Rich Text fields, those references will not automatically update. You have to handle them separately.

Localization: field-level, not entry-level

Contentful localizes content at the field level. A single entry can have different values for different locales on every field. The entry itself is one object. The locale data lives inside each field as a key-value map.

In the API response, a localized field looks something like this: the title field contains {"en-US": "Hello", "de-DE": "Hallo", "fr-FR": "Bonjour"}. Every field can be independently localized or not. Some fields might have values in all locales. Some might have values in only the default locale. Some might have values in three out of seven locales.

This creates a specific migration problem.

Not every CMS handles localization the same way. Some platforms use entry-level localization, where each locale gets its own copy of the entire entry. Some use field-level localization like Contentful but with different fallback behavior. Some use a hybrid approach.

When you migrate from Contentful, you need to understand exactly which fields are localized, which locales have actual content versus which are falling back to the default, and how the destination platform expects locale data to be structured.

On one of our projects, the client had 11 languages configured in their Contentful space. But only about 40 percent of entries had content in all 11 languages. The rest had content in two or three locales with the remaining locales falling back to English. The client did not know this. They assumed all content was fully translated because Contentful's UI shows fallback content seamlessly. The audit revealed that a significant portion of their "translated" content was actually just the English fallback being displayed.

That finding changed the entire migration scope. Instead of migrating 11 full locale sets, we migrated the actual translated content and configured proper fallback rules in the destination, which cut the migration timeline significantly.

Content type evolution: the accumulation problem

Contentful gives teams a lot of freedom in how they create and modify content types. You can add fields, rename fields, change field types (within constraints), and create new content types at any time. That flexibility is great for iteration.

Over time, though, it leads to accumulation.

A team creates a "Hero Banner" content type in year one. In year two, a different team member creates a "Campaign Hero" content type because they do not realize the first one exists or because it does not have the exact fields they need. By year three, there are three hero components, two CTA components, and four different card layouts, all doing roughly the same thing with slightly different field structures.

This is not a Contentful problem. It happens on every CMS. But Contentful's flexibility makes it especially easy for content types to proliferate without anyone noticing until the numbers get large.

When you migrate, you have two choices. You can migrate everything as-is, carrying all the duplication into the new platform. Or you can consolidate first, merging the duplicate content types into cleaner structures before migration.

The second option is almost always the right one. But it requires a full content audit to understand what you have, which content types are truly unique, which are duplicates, and which can be merged without losing functionality.

In a 31,000-entry migration we handled, the audit revealed 166 modules where the client expected roughly 40. We consolidated those down before writing a single migration script. That consolidation step alone saved weeks of transformation work downstream.

Cross-space references

Contentful supports cross-space references, which let entries in one space link to entries in another space. This is useful for teams that separate content by brand, region, or product line.

For migration, cross-space references add another layer of complexity. You are not just migrating one space. You are migrating multiple spaces with references between them. The migration order matters. The entries being referenced need to exist in the destination before the entries referencing them can be imported. And if the destination platform does not support the same cross-space linking model, you need to restructure those relationships entirely.

Most migration tools do not handle cross-space references out of the box. This is manual architecture work that requires understanding both the source and destination data models.

The export is just the beginning

Contentful provides solid export tooling. The contentful-export CLI can dump an entire space to JSON. The Content Management API gives you programmatic access to everything. The data is accessible and well-structured.

But exporting the data is the easy part.

The hard part is understanding the relationships, the locale coverage, the content type duplication, the Rich Text embedded references, and the circular dependency chains. That understanding is what turns a data dump into a successful migration.

Every Contentful space is different. Every content model creates a different set of structural challenges when you try to move it to another platform. A migration tool can move the data. But it cannot make the architecture decisions that determine whether the data works correctly on the other side.

That architecture work is what we do at Smuves. We are a Contentstack Preferred Partner, and content migration is our core focus. If you are evaluating a move and want to understand what your Contentful space actually looks like under the hood, we start with an audit.

Reach out at smuves.com if you want to talk through what a migration like this actually looks like.