Migrate content from a legacy CMS into Drupal

A custom or proprietary CMS can be moved into Drupal with the core Migrate API and Migrate Plus, but the effort depends on the source data, not the Drupal side. This page sets out what maps cleanly, which import paths are documented, and how to sequence the run.

On this page

What moving a legacy CMS into Drupal involves

Moving content out of a custom or proprietary CMS into Drupal runs through the core Migrate API, with Migrate Plus supplying source plugins for CSV, JSON, XML and SOAP feeds. The effort is driven mainly by two things: how cleanly the legacy data model maps to fields and entities, and how much content must be rebuilt by hand because it cannot be carried across. Before committing, the team running the legacy system should export a representative sample of content types, relationships, attachments and users, then count how many of those have no direct equivalent in Drupal. That count is the best early signal of scope. Drupal itself has no paid edition. It is licensed under GPL-2.0-or-later, so the cost of the project sits in people and infrastructure rather than in a Drupal license. The target content model is designed, not copied. Legacy tables, flags and free-text fields usually need to become content types, entity references, taxonomy terms and typed fields, and some legacy behavior will be better rebuilt as configuration than migrated as data. Decisions about URL structure, redirects, revisions and who owns each content type belong in the design phase, because they are expensive to change once migration mappings exist.

Work is scoped in writing before it starts. A useful written scope names each source system and export method, the content types in and out of scope, the field mapping document, the approach to users and passwords, the handling of embedded media and internal links, and the acceptance checks used to sign off each migration. Ask the delivery partner to state how incremental runs will re-import changed records, how rollback is tested on a copy of the target site, and which items the client must review by hand. Confirm with the vendor of your legacy CMS whether its export formats are stable and documented, because an undocumented export is often the largest unplanned cost in a project like this.

Which legacy CMS features map cleanly to Drupal and which do not

This table compares common legacy CMS features with their Drupal equivalents and flags the ones that rarely move without rework. Use it as a starting point, then confirm each row against your actual export, because the same feature name can be stored very differently from one custom CMS to another. The main takeaway is that structure and content usually map well, while embedded markup, workflow and layout logic need explicit design decisions.

Legacy CMS featureDrupal equivalentMigration notes
Content typesContent typesMaps cleanly if each legacy type has a stable set of fields. Confirm the field list per type in the export.
Typed fields (text, date, number, reference)Fields on content typesUsually maps cleanly. Check date formats, character encodings and reference fields that point to other records.
Categories and tagsTaxonomy vocabularies and termsMaps cleanly when values are controlled lists. Free-text tags need deduplication before import.
Attachments and imagesMedia or file entitiesMaps with planning. Confirm file paths, sizes and whether files are stored in the export or only referenced.
Authors and usersDrupal users and content authorshipMaps partially. Passwords usually cannot move, so plan password resets and check how author IDs are exported.
URLs and slugsPath aliases and redirectsMaps with planning. Build a redirect map from old URLs so existing links and search rankings keep working.
Revisions and version historyEntity revisionsPartial. Current versions usually migrate well, while full history often needs a deliberate decision about scope.
Editorial workflow states (custom)Content moderation workflowsDoes not map cleanly. Custom states and transitions must be designed and rebuilt, then mapped from stored status values.
Proprietary shortcodes or embedded markupText formats, filters or ParagraphsDoes not map cleanly. Each shortcode needs a converter or a rebuilt component, and it should be inventoried first.
Bespoke permissionsRoles and permissionsDoes not map cleanly. Rebuild role design from the access model, not from legacy permission flags.
Page layouts and templatesLayout Builder, Paragraphs or theme templatesDoes not map cleanly. Layouts are usually rebuilt as components rather than copied as stored markup.
Content stored as serialized blobsStructured fieldsDoes not map cleanly. Serialized data must be decoded and split into fields by a custom source plugin.

Documented import paths for a custom CMS source

A custom CMS export can reach Drupal through several documented paths, and most projects combine two or three of them. Each card below names what a tool reads, where it stops, and what your team must confirm before choosing it.

Core Migrate API with custom YAML

The core Migrate API lets you describe each migration as YAML, with a source, a process pipeline that maps each field, and a destination entity type. It is the base layer every other option plugs into, so the mapping you write here is what your editors will live with. Confirm which source plugins ship in your Drupal version and how your team will version the migration files. Note that the core API does not read your legacy CMS by itself; it needs a source plugin for the export format you actually have.

Migrate Plus for CSV, JSON and XML

Migrate Plus adds source and data parser plugins that read remote or local files in CSV, JSON and XML, including paged HTTP endpoints. It fits exports and simple APIs your CMS already produces. Its coverage of SOAP is the part to verify: confirm which SOAP request and response handling the version you install supports, and whether authentication, WSDL handling and pagination need custom code. Test against a full-size export, not a sample, because nested XML and JSON structures often break the field mapping late.

Migrate Source CSV for spreadsheet exports

Migrate Source CSV is a dedicated source plugin for comma-separated files, useful when the legacy CMS team delivers spreadsheets rather than an API. It handles one row per entity and maps columns directly to fields. It does not handle multi-value fields stored in a single cell, embedded HTML with line breaks, or relationships expressed as IDs from another file unless you add lookups. Confirm the character encoding and delimiter of each export before you write the mapping.

Migrate Tools Drush commands

Migrate Tools provides the Drush commands that run, roll back and reset migrations, and report what was imported. These commands are how you rehearse the cutover, rerun changed sources and undo a bad load without rebuilding the site. They do not repair a mapping error by themselves, and rollback only removes entities the migration recorded in its map table. Confirm how your team will store the migration map and whether runs happen from a pipeline or by hand.

Custom source plugin for unsupported formats

When the legacy CMS uses a proprietary database schema, a binary export or a SOAP service that the installed plugins cannot read, your team writes a custom source plugin in PHP. This gives full control over reading, paging and retry behavior, and it is where most of the unique effort sits. It also becomes code you must test, document and keep compatible with each Drupal upgrade. Ask who will own the plugin after launch and whether its tests run in your continuous integration pipeline.

How the source format changes the import work

CSV is the most common export from a legacy CMS and the simplest to read. Each row normally becomes one Drupal entity, such as a node, and each column is mapped to a field, a reference or a property. The format has no native nesting, so repeated values such as tags, authors or related items usually sit in one cell separated by a delimiter, and the import has to split and look up each value. Files are read in a single pass, so pagination and rate limits rarely apply, but very large files still affect memory and run time.

  • Row to entity: Confirm whether one row is one node, or whether several rows share a key and must be grouped into one entity.
  • Nested values: Multi-value cells need an explicit delimiter and a rule for how each part maps to a reference or a term.
  • Encodings and delimiters: Check the character set, byte order mark, line endings and whether commas appear inside quoted text.
  • Date formats: Spreadsheet exports often mix day-first and month-first dates, so agree one format and parse against it.
  • Duplicate keys: Find repeated legacy IDs or slugs before the run, since a duplicate key can overwrite a record or fail a unique constraint.

Before scoping, ask the legacy owner for a sample of the largest export and the rules behind each column, because hidden conventions in the spreadsheet usually decide the mapping effort.

How inventory, pilot, parallel run and cutover fit together

This sequence moves a legacy CMS into Drupal in controlled stages, so each stage proves the mapping, the content and the permissions before the old system is switched off. Each step closes with a check that content owners or your team can sign off.

  1. Inventory content types, fields, users and integrations

    DeployOpen works with your developers and content owners to list every content type, field, taxonomy, user role, file store and integration in the legacy CMS, including the ones that exist only in templates or custom code. Your team provides database or export access, a sample of real records and the names of the people who own each content area. The step is done when the inventory is agreed in writing and every item is marked as migrate, rebuild or retire.

  2. Agree the target content model in Drupal

    Using the inventory, DeployOpen proposes Drupal content types, fields, taxonomies, media types and roles, and records where legacy data will not fit a Drupal field without transformation. Your content owners review the proposal against how editors actually work, and your security or compliance lead confirms the role model. The output is a signed content model document that lists each legacy field and its target field.

  3. Build mappings for one pilot content type

    DeployOpen picks one representative content type, usually one with relationships, files and multiple values, and builds the source plugin, field mappings and any transformation code for it using the Migrate API and Migrate Plus. Your team confirms the source format is stable, whether CSV, JSON, XML or SOAP, and supplies a test extract. The pilot is complete when records load into a development Drupal site with no unmapped fields and the mapping is documented.

  4. Run full and incremental migrations into staging

    DeployOpen extends the pilot mappings to all content types, runs a full migration into a staging Drupal site, and then runs incremental migrations that pick up records changed in the legacy system since the last run. Migration IDs, source keys and rollback steps are recorded so any batch can be reversed and rerun. Your team provides a staging environment with enough storage for files and a database snapshot taken before each full run. The step closes when repeated runs produce the same results for unchanged source data.

  5. Validate counts, links, media and permissions with owners

    Record counts are compared between the legacy CMS and staging for each content type, and a sample of nodes is checked by content owners in the Drupal interface. Internal links, embedded media, file paths and user permissions are tested with scripted checks and manual review, and each defect is logged against its mapping. The step is complete when content owners sign off each area and every open defect has an owner and a planned fix.

  6. Run both systems in parallel with synchronized content

    The legacy CMS stays live for readers and editors for an agreed period while incremental runs keep Drupal current. Your team decides which system is the editing source for each content area during this period and communicates that rule to editors, since two-way editing is a major source of data loss. DeployOpen reviews sync logs, reruns validation after each cycle and tracks defects until the agreed exit criteria are met. The parallel period ends when the open defect list is empty for critical content and sign-off is recorded.

  7. Cut over with a final incremental run and redirects

    During a planned change window, the legacy CMS is set to read-only, a final incremental run brings Drupal fully up to date, and the validation checks run again. URL redirects from legacy paths to Drupal aliases are deployed and tested with a sample of old links, and DNS or routing changes are made according to your hosting plan. The step is done when the final counts match, redirects resolve, and content owners confirm the live site.

  8. Decommission the legacy CMS after handover

    Once the cutover is stable, the legacy CMS is archived according to your retention policy, with a final database and file export kept in storage you control, and its services, credentials and integrations are removed. Your security team confirms that access has been revoked and that backups of the old system are handled as your records policy requires. The step closes with a handover document covering the mappings, migration scripts, rollback procedure and open items.

What drives the cost and duration of a CMS migration

This table compares the factors that change the effort of moving content from a legacy CMS into Drupal, with what each one means for the work and what to ask for in a quote. Read it before comparing proposals, because duration is set by the written scope rather than by any fixed figure for this kind of project.

DriverEffect on effortWhat to ask for in a quote
Content types and fieldsEach source type needs its own mapping, transforms and validation. Nested or polymorphic structures add the most work.A field-by-field mapping list with the target Drupal content type for each source type
Quality of the exportClean CSV, JSON or XML is cheap to read. Broken encodings, inconsistent dates or undocumented SOAP responses add discovery and parsing time.Who supplies the export, in what format, and how the provider validates it before handover
Media volume and file storageLarge file sets take longer to copy, need checksum checks and may require path rewriting in body text.Total file count and size, storage location, and the approach to embedded media references
Users, roles and permissionsAccounts, password handling and role assignments need separate mapping and a decision on authentication after cutover.Whether passwords migrate, how roles map, and who signs off on access rules
Integrations and feeds to rebuildEach inbound feed, API or export job that the new site must replace adds build, testing and monitoring work.A list of every feed, its owner, and whether it is rebuilt, retired or kept running during transition
Custom shortcodes and embedded markupProprietary tags in body text must be parsed and converted, and each pattern adds rules and review.A count of distinct embedded patterns and the expected conversion approach for each
URL redirect requirementsPreserving old URLs requires a complete URL inventory, redirect rules and checks after launch.The URL inventory source, redirect rules expected, and how redirects are tested before go-live
Multilingual contentEach language adds translation linkage, language configuration and separate validation passes.Languages in scope, translation storage format and whether machine translation is excluded
Parallel runs and validation roundsEach trial import, comparison and fix cycle repeats mapping and checks. Scope should state how many rounds are included.The number of trial runs, the validation method, and the acceptance criteria for each round
Content cleaning by editorsEditorial review of duplicates, outdated pages and broken links often takes longer than the technical import.Who performs cleaning, what share of content is reviewed, and what the provider handles versus editors

What usually goes wrong in custom CMS imports and how to prevent it

Most failed legacy CMS imports do not fail in the Migrate code. They fail in the decisions around it: what was exported, what was lost in transit, what the old URLs were, and what changed while the work was underway. The cards below cover six failure points that recur on these projects, each with the check that prevents it.

Incomplete or undocumented export

A custom CMS often stores fields that no one listed in a spec, such as legacy flags, hidden categories or fields populated only by a cron job. If the export omits them, the Drupal site launches without data that editors assumed was there. Build a field-level inventory from the database schema and the templates, not from the admin screens, and record each field's destination in Drupal or its reason for being dropped. Ask the owners of the old system to sign off on that inventory before any mapping is written.

Data loss in embedded markup

Legacy bodies often contain inline HTML, proprietary shortcodes, serialized PHP or JSON blobs, and image tags pointing at internal paths. A source plugin that reads these as plain text will copy them without error, and the damage only shows when a template renders them. Sample a set of records from each content type, compare the rendered source with the imported result, and decide per pattern whether to convert, strip or keep it. Serialized fields need an explicit unserialize step in the process pipeline, with the failure cases logged.

Broken internal links and media paths

Internal links in legacy content usually use the old CMS's ID scheme or relative paths that resolved only on the old host. After import, those links return 404 pages or point to the wrong node. Build a lookup table from legacy IDs to new Drupal entity IDs during the run, and rewrite links in a post-processing step. Then crawl the new site after every run and check internal links, image sources and file downloads, so that a broken reference is caught in staging rather than by a reader.

Lost URLs and search equity

Drupal assigns its own paths unless the migration preserves the old ones through URL aliases, so a redesigned site can lose years of indexed pages. Export every public URL from the legacy server logs, sitemaps and analytics, not only from the database, because pages generated by routes often exist in no table. Map each old URL to a new destination in a redirect map and load it into the redirect module before cutover. Verify the mapping with a crawl that compares old and new status codes.

Users and permissions that do not carry over

Legacy user tables usually store password hashes in a format Drupal cannot verify, and role models rarely map one to one onto Drupal roles and permissions. Migrated accounts may arrive with no usable login, or with editors who hold roles they should not. Decide early whether accounts are migrated, re-created on first sign-in through an identity provider, or reset with a forced password change. Map roles on paper first, then test each role with a sample user in staging before the cutover.

Editors changing content during the parallel run

When the old system stays live while Drupal is built, editors keep publishing in it, and the new site drifts out of date. A single full import at cutover then either overwrites recent edits or misses them. Choose one of two approaches before the build: a content freeze on the legacy system for a defined window, or a delta process that imports records changed since the last run, keyed on a stable legacy ID and a last-modified timestamp. Agree who owns the decision and rehearse the cutover run at least once.

How rollback and incremental runs protect editor work

Rollback only removes what the migration created, and it finds those entities through the migration map. Migrate Tools records a row in the map for every source record it imports, linking the source ID to the Drupal entity ID it produced. A rollback reads that map and deletes the matching entities, so content an editor created by hand after the import is outside its reach. That protection holds only if the map is intact, so the team should avoid truncating map tables or editing migrations in ways that break the link between source and destination. Because a rollback is destructive, run it against a staging copy loaded from a recent production backup before any production run. Time the full import and rollback sequence there, check that node counts, media, redirects and URL aliases return to their pre-migration state, and confirm that no entities outside the migration were touched.

Incremental runs update only the source records that changed, and this is where editor work is most at risk. A migration configured to update existing rows will overwrite Drupal fields with source values on the next run, including fields an editor corrected after the first import. The mapping decides what happens. Fields can be set to skip on update, mapped only on initial creation, or left out of the update path, and the team should decide that per field before the first run. Confirm how each source identifies its records, whether through a stable primary key, an external ID or a file path, because an unstable identifier causes duplicates or wrong overwrites. Also confirm how the source handles deletions: a record removed upstream stays in Drupal unless the team plans an explicit unpublish or delete step, and that step needs its own test.

Questions teams ask before moving a custom CMS to Drupal

How much does it cost to migrate a custom CMS into Drupal?

Cost depends on the scope written into the quote, not on a standard rate. The main components are discovery of the source system, data analysis and field mapping, custom source plugins for the export format, content transformation rules, the Drupal content model that receives the data, test runs, reconciliation reports and cutover. Drupal itself has no license fee because it is GPL-2.0-or-later software, so the spend goes to people and any hosting you buy. Most of the effort is one-time, while hosting, monitoring and later content loads recur. Ask for a quote that lists each source entity, the number of mapping rules, the export formats, the number of test cycles and who signs off each stage. DeployOpen does not publish a price list, so a quote follows from that written scope.

How long does a legacy CMS migration take?

Duration is driven by how messy the source data is and how many content types must be mapped, not by the Drupal side alone. Expect the longest phases to be source discovery, cleaning inconsistent records, and repeated test imports that surface edge cases. Content volume matters, but the number of distinct content types, embedded media, custom fields and cross-references usually matters more. Incremental runs add time for synchronization planning and cutover rehearsal. Ask for a timeline tied to named milestones in the written scope, and confirm which parts depend on your team, such as export access and content owner reviews.

What must our team provide for the migration?

Three things decide how smoothly the project runs: access to the source data, named people who own the content, and a clear sign-off process. For access, you need a stable export or read-only database or API endpoint, sample exports in each format, and documentation of how the source stores relationships, revisions and files. Content owners must confirm which fields are authoritative, which content is obsolete and how mapped values should appear. Sign-off should be written, with an agreed acceptance checklist covering record counts, sample pages and reconciliation reports before cutover. Confirm these responsibilities in the scope document before work starts.

Can our own developers run the migration with the Migrate API?

Yes, a team with PHP and Drupal experience can build a migration with the core Migrate API and Migrate Plus, and many do. The routine work, such as defining a CSV or JSON source, mapping fields and running migrations, is well documented. The hard parts are a custom or SOAP source that needs its own plugin, transforming inconsistent data, handling entity references and revisions in the right order, building repeatable incremental runs, and designing rollback so that a failed import can be reversed without damaging later content. Your developers will also need to keep the migration code working as Drupal versions change, which is a recurring cost. Decide early whether that maintenance belongs to your team or to a partner.

Can Drupal import from SOAP or XML sources?

Drupal can import from XML and SOAP sources, typically through Migrate Plus source plugins for XML and through custom source plugins where the endpoint or message structure is unusual. SOAP is less direct because it requires calling the service, handling authentication and paging, and parsing the response into rows. Confirm during scoping whether your service returns complete data in a single call, supports pagination or change timestamps for incremental runs, and whether its WSDL is stable. Also check how attachments and binary content are delivered, since these often need separate handling.

What happens to our old URLs and search rankings?

Old URLs stop working unless you plan redirects, so the migration must include a complete URL inventory from the source system. Capture each public path, map it to the new Drupal path or alias, and generate permanent redirects that run in the web server or in a redirect module. Also carry over page titles, meta descriptions and canonical tags where the content still applies. Test redirects against a crawl of the old site before cutover, and monitor 404 errors for several weeks afterward. Ask your partner for the redirect map as a deliverable, because search rankings usually depend on it.

Do we need to run the old and new sites in parallel?

A parallel run is not always required, but it is a common way to reduce cutover risk. It means the legacy CMS and Drupal both serve content for a period while the team compares records, publishes changes on one side and reconciles them. The alternative is a single cutover after a final incremental run, which needs a freeze window on the source and a rehearsed rollback. Choose based on how often content changes, how critical the site is, and whether editors can tolerate a freeze. Write the decision and its rollback criteria into the scope before work begins.

How DeployOpen scopes and delivers a CMS migration

DeployOpen plans, builds, documents and supports self-hosted Drupal inside your own infrastructure, so the migration of your legacy CMS content runs in an environment you control. Engagements start with a written scope that fixes deliverables and timelines before any mapping or import code is written.

How we scope

  • We inventory the legacy CMS export formats, content types, media files and their volumes before agreeing a plan.
  • We document the source-to-Drupal field mapping for each content type, including fields with no clear equivalent.
  • We agree in writing which content, redirects and integrations are in scope, and which are excluded.

How we build

  • We build Migrate API and Migrate Plus migrations using CSV, JSON, XML or SOAP sources, with repeatable configuration files.
  • We test each migration on a copy of the production data, checking record counts, field values and file references.
  • We set up incremental runs and rollback procedures, and we verify rollback on a non-production environment.

How we hand over

  • You receive the migration code, configuration, mapping documentation and a runbook for running and rolling back imports.
  • We document known data gaps, transformations and manual cleanup steps so your editors and reviewers can check results.
  • We walk your team through the cutover checklist and the verification evidence collected during testing.

How we support

  • Post-cutover support, bug fixes and further migration waves can be arranged as a retainer, if your scope needs them.
  • Ongoing support covers Drupal updates, security patches and dependency upgrades for the migrated site.
  • Ask for a quote based on the written scope, including who runs the cutover and who owns the source system after launch.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.