A quick rundown of what using Drupal 6-to-8's migration UI feels like

Recently, I was asked to migrate a fairly straightforward website from Drupal 6 to 8: a blog; some feature articles; some static pages; a fair amount of uploaded images. I thought it would be a great candidate for reviewing what the Migrate UI bundled with core looks like, since its introduction in Drupal 8.1.0.

Although the Migrate framework more generally permits arbitrarily complex migrations, many people's first port of call will be the UI, so I thought it would be interesting to chart a user journey through its use: highlighting what went well, what went badly, and (most importantly) what looked problematic but turned out to be straightforward.

Kicking off a Drupal 6-to-8 migration with the UI

There's an excellent drupal.org guide page on running a UI-based migration into Drupal 8, and I'll try not to repeat too much of that here. The steps they describe are completely correct, although I just want to make a few comments on the experience of actually going through them.

A cautious beginning, then ploughing on

Before getting started, I made quite sure that my Drupal 6 and 8 installations reported healthy statuses, with no problems or warnings:

This includes trivial things like trusted host patterns: even if you don't think it might cause a problem, the fact that we have Drupal sites contacting each other and accessing each others' filesystems meant I wanted to be certain everything was working fine.

I then installed the three Migrate modules, and Drupal made it clear to me that they were experimental, which made me pause (which I think is good: to make sure you're happy with what you're doing):

I then navigated to /upgrade to run the migration as described on the d.o page. Despite the source D6 website being quite straightforward, the list of modules whose configuration was entirely unknown to the out-of-the-box migration procedure was a little worrying (click for bigger):

Some of those omissions made sense (admin_menu, jquery_ui, php, and a few custom modules); some of them were disappointing but expected (views, googleanalytics); some were a bit more worrying and looked like they might lead to content left behind (image*, date*). Still, in the spirit of adventure, I continued regardless.

First impressions

Well, the site still worked! and although the migration log was very verbose, there were no real surprises in it:

The log is linked directly from the "migration finished" page so it's easy to go and inspect the results: just don't be put off by the number of pages!

Configuration had been migrated: the site title, slogan and email address were immediately apparent. The files appeared to be in the same folder on the new website, and arbitrary user registration was still locked down.

One immediate problem I spotted was a warning about a missing block:

It turns out that the D6 website was using the Aggregator module: which core D8 has available to it, but isn't enabled by default. Unfortunately, enabling the module at this late stage caused fatal errors, and it was easiest to just roll back and re-run the migration with that module enabled. Ideally the 6-to-8 migration would've spotted that I had aggregator available (but disabled) in Drupal 8 and asked about it, but it was quick to re-run anyway.

It was made clear there was going to be no color migration; and theming is always radically different between Drupal versions: so as expected the theme and other visuals remained unchanged.

How well was content and users migrated?


Users were migrated OK and their passwords were preserved. However, the D8 site's admin user (UID=1) had been set up with a nonstandard username as recommended for security reasons; this was replaced with the (guessable "admin") username during the migration:

Where Drupal 8 had its own suitable analogue of an old Drupal 6 permission, configuration for that permission was copied across successfully. However, one slight problem was with the duplication of a particular role: the D8 Administrator role (which had special status in the new website) was joined by a lowercase "administrator" role (which had no particularly special status.) This meant that administrator users were not automatically getting the expected permissions in the new site.


All content types seemed to be migrated fine, along with their created dates and other field information. All node content was migrated into those types. Despite the warnings elsewhere in this blogpost, content migration alone was a major success!

One problem that plagued all content was that the text input formats from Drupal 6 had been migrated across into Drupal 8, to sit alongside the out-of-the-box formats. You'd think this was an advantage, but content noted as being in those formats (i.e. all rich-text fields) was not output to the page:

In the editing interface, the rich-text editor wasn't associated with the migrated input format, more evidence that the migration of configuration was the problem here:

When the input format is switched to one previously available in Drupal 8, unmigrated, then the rich-text editor kicks in:

And, after being saved, the content is visible:

The simplest resolution to this across all content was a post-hoc sweep of the input fields, changing the format name at the database layer, which I'll discuss below.


All image files were migrated as file entities, and the files were still present in the same place on the file system. Again, file migration was a major success and takes a lot of the usual heavy lifting out of how one would migrate content.

However, even with the media_entity module suite enabled at the point of migration, no media entities are created, and they're especially not created retroactively. In addition, inline images embedded with ImageBrowser were broken, because the filter was no longer functioning on the input format: this led to literal tags being displayed of the form [ibimage==...==...]. We'll discuss the fix for this below too.


No views were migrated:

which the eagle-eyed will have expected, but still seems a shame. I dread to think what might have become of Drupal 7 if there weren't a good upgrade path from CCK available when it came out!

It seems Views was dropped from core in part because it wasn't (could never be) complete, but this argument alone seems very "perfect the enemy of the good". Moreover, it's not consistent with a lot of core functionality: Dries himself admits image management in core is "basic", and one person's basic is another's partial. I'm not convinced it's compatible with the fundamental concept of contrib being there to extend core, either: if nothing in core is partial, then there's nothing to extend.

For those with many, many views to migrate, there's now a contributed project to migrate Views, which gives some hope. In the mean time, for the rest of us, the only way to have Drupal 6 Views in the new Drupal 8 website is to build them again from scratch, although there were some savings to be made, as we'll see below.

Quick fixes vs. building from scratch

Users and roles

Manual: the simple fixes here were: rename the administrator account; and reassign the administrator role. The fact that most permissions that made sense in both 6 and 8 were preserved was a boon here, although future work in the website would probably require further manual assignment of permissions new to Drupal 8.

Fixing input formats

Automatic: after diagnosing that a simple change of the input format sufficed, and discussing with the client to ensure it was OK to do so, all input formats for both node and comment body contents were changed to a Drupal 8 format.

This was automated with the following code. A postmigrate module was created, in line with the Extending and Altering Drupal post in my series of articles on Drupal 8 APIs. Put the following code in the file postmigrate.install in the module's folder:

 * @file
 * Install/uninstall hooks.
 * Implements hook_install().
function postmigrate_install() {
  // Change text format on all bodies to be basic HTML.
  $table_infixes = ['', '_revision'];
  foreach ($table_infixes as $infix) {
      ->fields(['body_format' => 'basic_html'])
    ->fields(['comment_body_format' => 'basic_html'])
  // Invalidate any node entity caches through a tag.
  $cacheTagService = \Drupal::service('cache_tags.invalidator');

This module runs SQL directly against the Drupal 8 database, and so is probably not recommended if you have a complicated data storage setup in D8. However, it should work fine otherwise, and after changing the input formats clears the caches for node pages, so their display will change to show the changed state of the content input format.

Rebuilding views

Mixed approach: because there were only a few views in the Drupal 6 site, and these had similar structures (differing only e.g. in content type). This made them ripe for an initial burst of manual

The process was as follows:

  1. Use drush config-export to export the site's entire configuration to a directory.
  2. Create an exemplum view, to be used as the model for several others.
  3. Use drush config-export again, to export configuration for this one view, and note the filename for it.
  4. Copy this file to the postmigratemodule, in subfolder config/install/.
  5. Make copies of the file in the same folder; rename them suitably, and edit them to match the differing requirements for path, filtering etc. (also changing the UUID each time: see this blogpost on fields for more info.)
  6. Uninstall and reinstall postmigrate to enable the views (deleting them first if they were already present).

Using this method, one initial view configuration was forked into four different files and edited each time, saving a lot of manual point-and-click work.

Fixing embedded images

Mixed approach: there were sufficiently few posts with embedded images that it was sufficient to create a view, filtering on any content whose body contains the word "ibimage". This view then served as a tool for administrators, to check any content with broken embed code in it, and rewrite it accordingly.


Drupal 6 to 8 migration is arguably ready, even though it might arguably not be complete. Migration of content and files—usually the worst bits of a Drupal upgrade—is implemented. Aside from a couple of bugs in input formats (to which your own situation might not be sensitive), this part of the migration bundled with core works well.

The experience of running a migration is generally OK, although there are a couple of points where you have to hold your breath and just move onto the next step. The long list of site elements that won't migrate (including date and image support modules), and the long list of log notices afterwards, both give more pause for thought than, it turns out, they needed to.

The experience of dealing with the aftermath of a migration is also good, although the omission of Views and media_entity entities will be a disappointment to some people all the same. Also, because so much of the Drupal 8 work required will be in the theming, it'll be important to manage expectations of what a newly migrated D6->D8 site will look like prior to theming work.

When I presented these findings to the Oxford Drupal User Group, someone who had done more migrations into Drupal 8 suggested that it was possible to use core's migration as a starting point: run a test migration; tweak content types and other structure to your satisfaction; export the migrated site's configuration; set up a brand-new site, using this configuration, using the config_installer install profile; then write a custom migration that would do the right thing, migrating into this new site. However, treating the migration UI as a "first pass" like this is clearly different from some people's expectations of what such a UI is intended to do.

Overall, though, it's great to have a UI that can handle so much of the major-version migration with only a few clicks, although (as with every major-version migration) it's important to be clear to potential users of the UI that it's never going to be a simple operation, even for simple sites.