Migration from Drupal to Wordpress
Even though we’d rather be helping our clients move from Wordpress to Drupal, we were recently asked to migrate a Drupal 7 website we built a few years ago to Wordpress. Whilst it made us a bit sad to see a good site go, we decided to bite the “olive stone” and we accepted the job.
The site consisted of a range of content types, each of them with several additional fields. In Wordpress content types and custom fields (of different types) are not available out of the box, so this often presents some challenges in how best to preserve that data in a way that makes sense/is usable in Wordpress post-migration. In addition, another important requirement of the migration was to maintain existing URLs as much as possible, or to put in place search engine friendly 301 redirects for cases where that was not possible. Additionally, some of the content types allowed more than one author to be attributed to an item of content (“people” (profile) was a distinct content type in the Drupal site, distinct from system users). This content - and a way of displaying it - needed to be preserved and rendered for Wordpress too.
After some deliberation and exploration of possible Wordpress plugins that could potentially help to automate some aspects of the migration, we decided to basically work from Drupal, and to use custom drush commands to export the data from Drupal, “massage” the data as needed, and import it into Wordpress using wp cli. We also created an additional database table to keep track of what has been migrated, and the resulting Wordpress ID, allowing us to rerun migrations if and when needed. We chose this approach because none of the existing Wordpress plugins or Drupal modules that can assist with migration work can handle anything beyond the most simple transitions.
Required Wordpress plugins
As all the different Drupal content types needed to be migrated as standard Wordpress posts, with no custom fields to hold the incoming data, the required Wordpress plugins were just two:
Co-authors Plus: this plugin enables the creation of “co-authors” either based on users or standalone values, and then to assign an indefinite number of those co-authors to a post. Ideal for what we needed;
Custom Permalinks: this plugin allows us to assign a completely customised permalink to a post, something we needed in order to assign Drupal-created pathaliases as permalinks of the new WP posts.
Our migration process
The first step of our migration was the migration of the Drupal 7 users to Wordpress users. We used a custom drush command using an entity query to get the user account data our of Drupal and some custom processing to turn it into the form that wp cli’s wp user create requires it. Our custom drush command called wp user create for each user to be created. After successful user creation, it saved the Drupal user ID, the entity type (user), the Wordpress ID and the Wordpress type (user) in a separate database table, so that we could keep track of migrated users.
In our Drupal 7 site authors were simply nodes of the content type “people”. We first migrated these to become Wordpress users, although strictly speaking that might not have been necessary in some scenarios (we could have created co-authors directly). The process was the same, but with some simple checking against existing (already migrated) Wordpress users, to avoid duplicates. As with users, we kept track of the migrated authors (Drupal “people”) and the respective Wordpress users.
After successful migration we created co-authors for all users, this also enabled us to use existing users as co-authors.
The site had a number of vocabularies with a lot of terms. Two of them free-tagging, which were migrated to Wordpress as “tags”, and the rest fixed, which were migrated as Wordpress “categories”.
To be able to run the migration in a more finely-tuned way, the custom drush command took the vocabulary as an argument and doing this allowed us to migrate one vocabulary at a time.
At least one of the Drupal vocabularies was hierarchical, so we had to preserve and rebuild this hierarchy in the corresponding Wordpress category.
For each taxonomy term we used wp term create to create the term in Wordpress, either as tag or as category. For hierarchical vocabularies, we needed to first make sure that the parent existed in Wordpress, to pass the Wordpress parent id to wp term create, thus replicating the Drupal hierarchy in Wordpress.
The Drupal 7 site had several thousand attached files, which all needed to be imported as “Media” into Wordpress. The approach was the same: a custom drush command making use of an entity query to go through all files managed by Drupal, and calling wp media import for each file to create it as a media item in Wordpress.
Again, it was crucial to keep track of migrated files and their respective IDs so that we could associate the “media” with the correct “posts” later and also to allow us to rerun the migration subsequently if needed.
5. Content migration
Finally, we were ready to migrate the content. Again, our custom drush command took the Drupal content type as argument.
Content migration was a bit more complex, as there were more fields to deal with such as, referenced taxonomy terms, inline images in the body of a node, links in the body of a node, the multiple authors, etc. So the “data massaging” for each node included getting the respective Wordpress IDs for co-authors, rewriting links to images or files to where they are stored in Wordpress, getting the slugs (URLs) of the respective Wordpress tags and so on. We also had to rewrite embedded videos – the Drupal site used a media field for external videos – to the “Wordpress way” and add these to the body of the Wordpress post. In the case of attached files, which would not be displayed in Wordpress out of the box, we also added some html to the post body to provide a link to the file.
Once ready, we called wp post create to finally create the post, including a post excerpt, the relevant Wordpress tags and categories. After that, we had to add the post ID as parent post to media that was attached to the post (as media are also posts in Wordpress, we used wp post update to add the post_parent), also had to add the co-authors to the post (using wp post meta add) and finally the custom permalink (with the same call to wp post meta add)
6. Cleaning up
As Wordpress tags and categories serve a useful purpose for structuring the content and navigation, we decided against custom permalinks for terms in order to preserve the URL (also because while in Drupal it is not a problem to have a term at path /xxxx and a node at path xxxx/yyyy, this does cause problems with access to posts in Wordpress). So we used a drush command to add 301 redirects for all Drupal terms to the respective Wordpress tag or category to .htaccess.
As we are predominantly Drupal coders, starting off at the Drupal end and using custom drush commands to export and massage data was the best way to go as this used our best-developed knowledge and experience and it gave us full control over the data we wanted to pass to Wordpress. At the Wordpress end, using wp cli helped us to make ensure data was added “the Wordpress way”, and we could leave Wordpress itself to deal with its internals.
In this specific case, the client asked someone else to carry out their Wordpress development as part of a wider rebranding exercise. However Netuxo has been working with Wordpress for years to deliver some of our very simple projects and we now have a very experienced WP developer in the team.