CCelestiallines.com
← Back to blog

How to Migrate a Website to Drupal 10

By Pawan Singh · Jun 2026

Migrating an existing site to Drupal 10 is really two separate problems — moving content, and rebuilding whatever the old platform did with markup, plugins, or theme logic that Drupal doesn't have a direct equivalent for. Underestimating the second one is the most common way these projects run over budget.

Where You're Migrating From Changes Everything

  • From Drupal 7 or 8/9 — Drupal core includes a built-in Migrate module specifically for this, with upgrade paths and UI tooling (drush migrate:upgrade plans a migration automatically from a Drupal 7 database connection). This is the smoothest path of any migration source, since the underlying data model is already Drupal-shaped.
  • From WordPress — no fully automated core tooling; typically done via a custom Migrate API plugin reading from WordPress's database/REST API directly, or by exporting content (via WordPress's XML export) and writing a script to transform and import it into Drupal's content types.
  • From a fully custom or legacy CMS — usually the most work: export content to a structured intermediate format (CSV, JSON) first, then import via Drupal's Migrate API or a custom script using the Entity API directly.

Plan the Content Model Before Migrating Anything

Map every content type, field, and taxonomy from the old site to its Drupal equivalent before writing any migration code. This is where "how the old site actually structured its data" and "how Drupal thinks about content" most often diverge — a WordPress custom field group doesn't map 1:1 to a Drupal field group, and getting this mapping wrong means re-running the entire migration later once you notice the mismatch.

Using Drupal's Migrate API

For anything beyond a trivial import, the Migrate API (built into core) is the right tool — it's declarative, repeatable, and rollback-able, unlike a one-off script:

id: legacy_articles
source:
  plugin: url
  data_fetcher_plugin: http
  data_parser_plugin: json
  urls:
    - 'https://old-site.example.com/api/articles.json'
  item_selector: articles
  fields:
    - name: title
    - name: body
    - name: published_date
  ids:
    title:
      type: string
process:
  title: title
  body: body
  created: published_date
destination:
  plugin: 'entity:node'
  default_bundle: article

Run it with drush migrate:import legacy_articles, and roll it back cleanly with drush migrate:rollback legacy_articles if something's wrong — this rollback capability is exactly why hand-rolled import scripts are worth avoiding for anything beyond a handful of records; a bad import via a raw script usually means manually deleting nodes to undo it.

URL Structure and SEO Preservation

The single most consequential migration mistake for an existing site with search rankings: letting URLs change without redirects. Map every old URL to its new Drupal path alias, and set up 301 redirects (via the core Redirect module, or handled at the reverse proxy/web server level) for every URL that changes. Losing indexed URLs without redirects means losing accumulated search ranking, not just breaking a few bookmarks — this is often a bigger business risk than any technical migration challenge.

Images and Media

Media files need their own migration path — a file or media destination plugin that downloads/copies the actual binary files while creating the corresponding Drupal media entities, not just migrating a text field that happens to contain an image URL. Verify a sample of migrated images actually resolve correctly (dimensions, alt text carried over) before considering media migration complete.

Theme and Custom Functionality

A theme rarely migrates directly — Drupal's templating (Twig) and theme layer work fundamentally differently from most other platforms' theming systems. Budget for rebuilding the frontend against the new content structure rather than assuming an existing theme can be "ported." Similarly, any custom plugin functionality from the old platform needs a Drupal-native equivalent — a custom module, a contrib module covering the same need, or in some cases a decision that the feature isn't worth rebuilding at all.

A Practical Migration Sequence

  1. Audit and map every content type, field, and taxonomy to its Drupal equivalent.
  2. Build and test the Drupal content model (content types, fields) before importing anything.
  3. Write and test the migration on a small content sample first, not the full dataset.
  4. Migrate media/files alongside content, verifying a sample renders correctly.
  5. Map every old URL to its new alias and set up redirects before going live.
  6. Run the full migration, verify counts match the source, and spot-check content across every type.
  7. Keep the old site's database/export archived, not deleted, for a reasonable window after cutover in case something was missed.

Common Issues

  • Migrated content missing fields — a mapping gap between source and destination fields in the migration's process section, usually from a field type mismatch (e.g. plain text mapped to a formatted long-text field without setting the format).
  • Duplicate content on re-running a migration — the migration's ids configuration doesn't uniquely identify source rows, so Drupal can't tell it's re-importing the same item rather than creating a new one.
  • Massive drop in organic search traffic after launch — missing URL redirects; this is preventable, not something to discover after the fact.
  • Broken images after migration — file migration ran before the corresponding media entities existed, or file paths in migrated content weren't rewritten to Drupal's actual file storage paths.

Planning a migration to Drupal, or need help with one already underway? Get in touch — I work as a freelance Drupal developer.

Comments