When you move a Drupal site to a headless CMS, the thing most likely to break silently is not your content. It is the invisible wiring between pieces of it. Drupal stores relationships as numeric IDs (node 4127, term 88, media 903), and almost every headless platform mints brand-new IDs the moment you import. So every taxonomy tag, every embedded image, every "related articles" block that pointed at an old number now points at nothing, or worse, at whatever content happened to inherit that number. The pages still render. The links inside them just quietly go nowhere.
Here is the scene that keeps happening. The migration "succeeds." Every article is present, the word counts match, everyone high-fives. Then two weeks later someone notices the related-content sidebar is empty on 8,000 pages, half the article hero images resolve to a 404, and the tag pages that used to rank now list either nothing or the wrong articles. Nobody broke anything on purpose. The references just forgot who they pointed to.
Think of it as an office move where everyone's card lists an extension
Imagine your whole company runs on internal phone extensions. Nobody's business card says "call Dana in accounts payable." It says "call 4127." That works beautifully right up until you move buildings and the new phone system renumbers every extension from scratch. Now 4127 rings the mail room. Everyone's cards are technically valid numbers pointing at completely wrong people, and you will not find out until someone tries to reach Dana and gets a very confused intern.
That is exactly what a Drupal node ID is. It is an extension, not a name. Contentful, Sanity, Strapi, Payload: they all hand out fresh extensions on arrival. Your job in the migration is not just to move the people. It is to reprint every business card.
Where the wiring actually lives
Drupal is unusually reference-heavy, which is why it is powerful and why it is a pain to leave. Three kinds of references cause almost all the pain, and they fail in different ways.
| Reference type | How Drupal stores it | What breaks in headless |
|---|---|---|
| Taxonomy terms | Term ID (tid), often in a hierarchy of parent tids | Tags reattach to wrong terms; parent/child nesting collapses; tag landing pages empty out |
| Media (images, files, docs) | Media entity ID plus a separate file ID, referenced from fields and from inline HTML | Inline <img> and file links keep old paths; alt text and focal points get dropped; assets orphan |
| Entity references (related content, authors, paragraphs) | Target entity ID inside a reference field | Related-content links go null; author bylines detach; Paragraph components lose their nesting |
Notice the pattern. Taxonomy and entity references break by pointing at the wrong new ID. Media breaks in a second, sneakier way: rich-text body fields contain hard-coded old paths and inline entity embeds that no import mapper touches unless you tell it to. You can migrate every image perfectly into the new asset library and still ship articles whose body copy points at /sites/default/files/ URLs that no longer exist.
The trap that gets everyone: order of operations
You cannot wire a reference to something that has not arrived yet. If you import articles before you import the taxonomy terms they point to, the mapper has nothing to resolve against, so it either drops the reference or invents a placeholder. Taxonomy first, then media, then content, then the relationships between content. Terms before the things tagged. Images before the articles that embed them.
And you need an ID map: an old-ID-to-new-ID lookup table you build during import and keep. This is the single most valuable artifact of the whole project and the one most teams throw away. It is the phone directory that says extension 4127 is now Dana at 8802. Without it, every reference is a guess, and every redirect you write later is a guess on top of a guess.
How to catch it before go-live, not two weeks after
The honest fix is counting, then re-counting. Before you migrate, count how many nodes carry each taxonomy term, how many media entities are referenced versus orphaned, and how many entity-reference fields are populated. After you migrate, count again. The numbers should match. When 6,200 articles had a primary category before and 4,900 have one after, you have found 1,300 broken references while you can still fix them cheaply.
Inline media is the one you will forget, so parse the body HTML specifically. Look for old file paths and inline entity embeds and confirm each one resolves in the new asset library. This is exactly the kind of relationship mapping and orphaned-asset detection a pre-migration crawl is built to surface, so the empty sidebars show up on a report instead of in an angry Slack message from your traffic lead. There is more on the mechanics in our migration planning guides.
What I'd actually do
Build the ID map first and treat it as the deliverable, not a byproduct. Migrate in dependency order every time: terms, then media, then content, then references, and validate counts at each stage rather than at the end. Parse every rich-text body for hard-coded paths and inline embeds, because the field-level references will lie to you by looking clean. And do a full reference-integrity pass on staging before anyone signs off, because "all the pages are there" is not the same claim as "all the pages still point at the right things."
If that sounds like more bookkeeping than moving, that is the honest shape of a Drupal migration. The content was always the easy part. The hard part is that in Drupal, everything knows everyone else by their extension number, and you are the one who has to call every desk in the new building to find out who actually sits there now.
