Drupal case study · 03

Nonprofit Publishing Website & Content Migration

A nonprofit publisher needed years of articles from separate WordPress and HubSpot sites brought together on one Drupal publishing platform without interrupting ongoing publishing. As part of the development team, I designed and built the repeatable migrations that carried articles, authors, images, categories, and publishing state through sandbox, staging, and production. The consolidated site serves approximately one million page views per month.

Organization
A nonprofit publisher with years of evergreen articles spread across separate HubSpot and WordPress sites, each with its own domain, archive, and publishing tools.
Need
Bring those blogs and archives onto one Drupal site and one domain while one of the sources kept publishing throughout the project.
Solution
A repeatable Drupal migration for both sources: exported WordPress content for a planned cutover and batched API imports for the live HubSpot content, with custom handling for articles, authors, images, categories, and publication state.
Result
The blogs and their full archives were consolidated under one domain, with one Drupal editorial workflow for drafts, moderation, revisions, and publishing.
My role
Designed and built the migrations, including custom plugins, HubSpot API integration, data transformation, author matching, and reconciliation. Other developers built the rest of the site.
  • Migrate API
  • Entity API
  • Plugin API
  • HTTP Client
  • Views
  • Taxonomy
  • File API
  • Content Moderation

The challenge

A nonprofit publisher had built up years of articles across separate publishing systems and domains. Some blogs lived on HubSpot while others lived on WordPress sites, each with its own address, editorial tools, and archive.

Much of the material was evergreen. Articles published years earlier were still drawing readers, so the migration needed to preserve the full archive rather than only recent content. The goal was to bring those blogs together on one Drupal site and one domain, and HubSpot kept publishing new articles while the new site was being built.

The solution

Our team built a new Drupal publishing website, and I was responsible for getting the content into it. I designed the migration as a repeatable process rather than a one-time import, so it could run in sandbox, staging, and production as the site took shape, picking up newly published HubSpot content on each run.

The migrations preserved articles, authorship, images, categories, and publishing state, and placed imported content into Drupal’s normal editorial workflow so editors could manage it like any other article after launch.

Results and capabilities

The blogs and their archives were consolidated under one domain and one Drupal site, which serves approximately one million page views per month. Readers could find the organization’s articles in one place instead of moving among separate sites.

Editors gained one Drupal workflow for drafts, moderation, revisions, and publishing across content that had previously lived in different systems. Older articles also became part of the site’s active content experience instead of remaining separated across legacy archives.

My role

This was a team project, and the migration was mine. I designed and built the imports for both platforms, including the migration configuration, custom source and process plugins for both sources, and a custom destination plugin for authors. That custom processing transformed incoming content, resolved authors, handled publication state, and moved files into Drupal.

For the live source, I also built the authenticated API handling needed to retrieve content in batches using paging and date ranges, and I reconciled each run against the source. I contributed to the day-of-year ordering and calendar archive used to present the migrated content, and my work on the site continued through application development and ongoing production support. Other developers built the rest of the site around the migrated content.

Technical implementation

Articles, author accounts, author profiles, and categories became normal Drupal content as the migration ran. Author matching required special handling because the same person could appear in both sources, might not exist cleanly in the source user data, or could arrive without a usable email address.

The two sources also required different migration strategies. WordPress could be exported and treated as fixed for the final cutover, while HubSpot was still publishing and had to be read repeatedly through an authenticated API using paging and date ranges. I built the custom handling around that API and configured the migration so its source endpoint could change by environment, allowing the same migration setup to run in sandbox, staging, and production.

How the migration ran

The work moved through four stages. We first ran the migration in a sandbox and built the site on top of real migrated content. That proved the approach before committing to the final cutover. Staging followed later, with another live-source import picking up content published since the earlier run. When the new site was ready, the customer froze publishing and we performed the final migration and domain switch.

Because the live-source migration ran more than once, every run needed to behave predictably. Authors were matched using stable source identifiers, previously fetched images were reused, and content with an unresolved author received a defined fallback instead of stopping the import.

After each run, I reconciled the results against the source: exported rows were compared with articles created, and the live platform’s record total was compared with what had landed in Drupal.

Drupal under the hood

Migrate API and custom plugins
Custom source, process, and destination plugins moved articles, authors, and profiles into Drupal while transforming dates, publication state, author identity, and other source data.
HTTP client and configuration
Handled authentication, paging, and date-range requests needed to migrate content from the live API.
Entity, Field, and Taxonomy APIs
Turned imported articles, authors, profiles, and categories into normal Drupal content that editors could manage after launch.
File API and Content Moderation
Reused previously downloaded images across repeated runs and placed imported articles into the appropriate publishing state.

Why I chose this approach

I designed the migration as a process we could rehearse and repeat, not as a one-time import at the end of the project.

The organization was still publishing while the new Drupal site was being built, so waiting until launch day to move everything would have created too much risk. Running the migration early let the team build the site around real content, and the reconciliation after each run caught gaps while there was still time to fix them. That in turn meant every run had to behave predictably, which is why authors, files, and incomplete records each had defined, repeatable handling.

The two source systems fit into that process differently. Handling each according to how it actually behaved, with WordPress as a fixed export and HubSpot as a live API read on every run, let publishing continue during the project while still arriving at one complete Drupal site at launch.