Migrate a drupal 6 website to drupal 7

Submitted by victor.bourgade on

Almost anywhere on the web you'll find that migrating a drupal 6 website to drupal 7 is a nightmare, and that's quite true ! So what you have to do is keeping it easy and follow this tutorial which will try to make it easier for you. But remember that any migration method is not 100% bullet proof and you'll always have some manual work to do after. You'll probably also have to redesign your theme.

1. Making a copy of the site structure

What you have to do first is making the copy of your D6 site structure. I'm talking in term of files and database structure. Assuming you have a fresh drupal 7 installation set up on your server, we now need to implement everything to make our installation ready for content migration. From D6 to D7 :

  • Copy your sites/default/files folder.
  • Copy your sites/all/themes folder.
  • Don't copy your "modules" folder, find the equivalent drupal 7 version of each of your modules, install them and configure them. 
  • Manually recreate all your content types, taxonomy terms, user profiles, views etc.

The aim of this step is to get an empty website with the exact same configuration of your D6 version. Hopefully most of modules have a D7 equivalent but thinking about migrating their configurations programmatically doesn't worth it. So it's a step we have to make by hand. You can obviously make dummy content to test your configurations.

Content type migration from d6 to D7
Example for a content type "news"

2. Migrating the content

2.1 Understanding the table structure differences

Now we have the same skeleton for both website, the same content types with the same fields, the same taxonomy terms, and same modules we can migrate the content. One of the major difference between D6 and D7 is that the page structures are stored in the database per content type whereas in D7 pages are an aggregation of fields.

Drupal 6 simplified database structure


Drupal 7 simplified database structure
node_type news
node nid
field_data_field_department nid
field_data_field_article_value nid
etc etc

So basically you should have understood that on drupal 7 pages are actually a call to all the fields which have the same "nid" (node id). So the structure is a bit more complex. Nodes are called "entities" on drupal 7, such as taxonomy terms or users and means that they are actually a complex entity with several fields and configurations attached, all linked by an "id", nid for node, tid for taxonomy term, uid for user etc.

So our main job here is to split the simple database structure of D6 in a complex one for D7 by putting all the datas at the right place. It sounds complicated but after making one content type by yourself you'll understand better how it works.

2.2 Migrating the content with drush updb

My personal choice was to use hook_update_N to do the migration, so I just had to run drush updb and the job will proceed. Here is a snippet you can use and adapt to your needs :

function MODULENAME_update_7001() {
   // Setup informations to access the D6 database.
   $servername = "YOUR SERVER NAME / localhost if on local";
   $username = "DATABASE USERNAME";
   $password = "PASSWORD";
   $dbname = "DATABASE NAME";

   // Create connection
   $conn = new mysqli($servername, $username, $password, $dbname);
   // Check connection
   if ($conn->connect_error) {
       die("Connection failed: " . $conn->connect_error);

   // The sql query to the D6 database
   $sql = "SELECT * FROM content_type_news ORDER by nid ASC";

   if ($conn->query($sql)) {
       echo "Record fetched successfully \n";

       $result = $conn->query($sql);

       $num = $result->num_rows;
       // We print how many fields will be imported.
       echo "There are " . $num . " fields to be imported \n";

       $attempts = 0;
       set_error_handler("exception_error_handler", E_WARNING);
         do {
               while ($row = $result->fetch_assoc()) {
                 // We often need to go in the node table to fetch some datas
                 $row_comp = db_query('SELECT * FROM node WHERE nid = :nid', array(':nid' => $row['nid']))->fetchAssoc();
                 // Insert the values in the respective places
                   'entity_type' => 'node',
                   'bundle' => $row_comp['type'],
                   'deleted' => 0,
                   'entity_id' => $row['nid'],
                   'revision_id' => $row['vid'],
                   'language' => 'und',
                   'delta' => 0,
                   'field_department_value' => $row['field_department_value']
                 // We print everything the field has been well imported
                 echo "Field date for node " . $row['nid'] . " imported \n";
             } catch (Exception $e) {
                 // If error we print that something went wrong.
                 echo "Update failed for " . $row['nid'] . "\n";

         } while($attempts < $num);

   } else {
       // Connexion to the D6 database issue
       echo "Error fetching record: " . $conn->error;
   // We close the connexion at the end of the hook_update_N

Obviously it is sometimes a bit more complicated and you'll need to make custom functions to update a specific field table but globally you should be able to do what you want with this snippet.

2.3 The good order of importation

From my experience you should respect some ordering when importing the content. The main example is that you'll need a uid for importing the nodes (to set the author). Unfortunately you can't have it without passing an extra external query to the D6 database. That's why you should import the user before the node so you can fetch the uid from the D7 database (that's the purpose of the $row_comp variable in the previous code).

The order to respect is : User -> Nodes (and fields ?) -> Comments -> Files.

As example, here is the order I used :

  1. Users and user's pictures.
  2. User's fields.
  3. User's roles
  4. Nodes and nodes revisions 
  6. Files (in D7 paths become relatives to public:// so you'll need to alter them during the import process)
  7. All fields for nodes and their revisions
  8. Aliases for pathauto

You can use this simple function to make the path relative to public :

function _file_path($filepath) {
   // $filepath = $row['url'];
   $uri = substr($filepath, 20);
   $public_uri = 'public://' . $uri;

   return $public_uri;

2.4 Rebuild your menus

Personally I didn't import the menu programmatically as it was messing everything up with pathauto. I was getting basic url shape (i.e node/nid) in the browser toolbar whereas I wanted the aliased version of the url.

So I have built them manually. To do that, you have to import all your content and build the menu manually. Why ? Because drupal is complaining if you make a menu link to a non existing content, so the page needs to exist.

If like me you need to get a complete website in one shot, I recommend you to do it before the real migration date. You'll just have to clean your database after having built the menus and you'll be able to re-import the content later so the menus will be already there.

Import the content -> build the menu -> clean the db from the content.

So you still have a clean fresh version of your db kindly waiting for the content to be migrated.

3. Rebuild your theme

Unfortunately from D6 to D7 the HTML structure change a lot and you will have to rebuild a big part of your theme.

Maybe it's the good time to make a brand new theme and show people how your drupal 7 website rocks ?!

The good point is by rebuilding your theme you'll be able to make a tour of your website and see if anything went wrong from the database migration. You'll can then compensate issues by manual changes.

That's it ! You should have now a complete migrated website and you can congrat yourself because it was a hard task.

I hope you've enjoyed this tutorial.


Drupal 6 Drupal 7 Migration

About the writer


Victor is a web developer passionnated in drupal and bootstrap technologies. He likes challenges and beautiful designs.

When not behind his computer you'll find him drinking beers with friends or in the middle of nowhere hiking with his dog.