> ## Content Index
> Fetch the complete content index at: https://toodlepip.co.uk/llms.txt
> Use this file to discover other available public pages before exploring further.

# Migrating from Drupal 6 to WordPress: Part 2 – Content, Tags, Links and Images
- URL: https://toodlepip.co.uk/migrating-drupal-6-wordpress-part-2-drupal-wordpress/
- Published: 2014-01-13T09:45:46.000Z
- Updated: 2014-01-13T13:16:48.000Z
- Author: Sam Michel
- Tags: Geekery, drupal, migration, wordpress, #wp, #wp-post, #Import 2026-10-23 15:06

This is second in a series of posts about [migrating from Drupal 6 to WordPress](https://toodlepip.co.uk/migrating-drupal-6-wordpress-step-step-notes/). The first post tackled [installing WordPress and some basic security options](https://toodlepip.co.uk/migrating-drupal-6-wordpress-step-step-notes/).

This time it’s a gnarly bunch of SQL for converting content out of Drupal 6 into WordPress version 3.7\. It should also work with the latest version (at time of writing WordPress 3.8 Parker).

Thankfully, despite the [scant notes on WordPress’ Codex](http://codex.wordpress.org/Importing%5FContent?ref=toodlepip.co.uk#Drupal), there’s some great reference material for the Drupal to WordPress migration. Huge thank to [Scott Anderson](http://blog.room34.com/archives/4530?ref=toodlepip.co.uk), [Mike Smullin](http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/?ref=toodlepip.co.uk) and [Nile](http://blondish.net/how-to-convert-drupal-to-wordpress/?ref=toodlepip.co.uk) (*see* [*references*](#References)).

## Convert from Drupal to WordPress

I used [Scott Anderson’s SQL](http://blog.room34.com/archives/4530?ref=toodlepip.co.uk) as the basis for the transfer of content from Drupal to WordPress.

First up, some assumptions:

- Some basic SQL knowledge will be a big help, some of the lines below will need to be updated to fit your site (there’s a [great primer here](https://www.udemy.com/mysql-database-for-beginners2/?tc=blog.sql.cta1.f&popup=no&utm%5Fsource=blog&utm%5Fmedium=udemyads&utm%5Fcontent=post3435&utm%5Fcampaign=content-marketing-blog&xref=blog)).
- The WordPress database is called *wordpress* with a table prefix of *wp\_* (think these are the defaults)
- The Drupal database is called *drupal* with no table prefix
- Both databases are on the same server and accessible using the same database user

and the big assumption:

**This is a fresh install and you’re moving all of the content from the original Drupal site to the new WordPress one. Merging content is a whole different beast and well outside the scope of this post.**

### Clear Out WordPress

Assuming that it’s a case of moving everything lock, stock and barrel. Any content in the WordPress database needs to be zapped:

\# Empty previous content from WordPress database.  
TRUNCATE TABLE wordpress.wp\_comments;  
TRUNCATE TABLE wordpress.wp\_links;  
TRUNCATE TABLE wordpress.wp\_postmeta;  
TRUNCATE TABLE wordpress.wp\_posts;  
TRUNCATE TABLE wordpress.wp\_term\_relationships;  
TRUNCATE TABLE wordpress.wp\_term\_taxonomy;  
TRUNCATE TABLE wordpress.wp\_terms;

In this case there’s only one author, but if migrating multiple authors, there are options in the [original script](http://blog.room34.com/archives/4530?ref=toodlepip.co.uk) to handle this.

### Tags

Next, it’s time to migrate the tags from Drupal, using the SQL below. WordPress is a bit fussier than Drupal about tags, so they need to be forced to lowercase and spaces

stores tags in the database in lower case, Drupal doesn’t care, so some munging is required.

\# Using REPLACE prevents script from breaking if Drupal contains duplicate terms.  
  
REPLACE INTO wordpress.wp\_terms  
 (term\_id, \`name\`, slug, term\_group)  
 SELECT DISTINCT  
 d.tid, d.name, REPLACE(LOWER(d.name), ' ', '\_'), 0  
 FROM drupal.term\_data d  
 INNER JOIN drupal.term\_hierarchy h  
 USING(tid)  
 INNER JOIN drupal.term\_node n  
 USING(tid)  
 WHERE (1  
 \# This helps eliminate spam tags from import; uncomment if necessary.  
 \# AND LENGTH(d.name) < 50  
 )  
;  
  
INSERT INTO wordpress.wp\_term\_taxonomy  
 (term\_id, taxonomy, description, parent)  
 SELECT DISTINCT  
 d.tid \`term\_id\`,  
 'post\_tag' \`taxonomy\`,  
 d.description \`description\`,  
 h.parent \`parent\`  
 FROM drupal.term\_data d  
 INNER JOIN drupal.term\_hierarchy h  
 USING(tid)  
 INNER JOIN drupal.term\_node n  
 USING(tid)  
 WHERE (1  
 \# This helps eliminate spam tags from import; uncomment if necessary.  
 \# AND LENGTH(d.name) < 50  
 )  
;

### Posts, Pages & Tags

The next part is properly magic, transforming Drupal content into posts that WordPress can handle. Watch out for the line:

WHERE n.type IN ('post', 'page', 'blog')

Add in any other content types that are used in the Drupal installation. For example, older installations, often use a story content type. If so, alter the above line to:

WHERE n.type IN ('post', 'page', 'blog', 'story')

the main code below does the magic and also keeps any unpublished posts hidden.

\# Keeps private posts hidden.  
INSERT INTO wordpress.wp\_posts  
 (id, post\_author, post\_date, post\_content, post\_title, post\_excerpt,  
 post\_name, post\_modified, post\_type, \`post\_status\`)  
 SELECT DISTINCT  
 n.nid \`id\`,  
 n.uid \`post\_author\`,  
 FROM\_UNIXTIME(n.created) \`post\_date\`,  
 r.body \`post\_content\`,  
 n.title \`post\_title\`,  
 r.teaser \`post\_excerpt\`,  
 IF(SUBSTR(a.dst, 11, 1) = '/', SUBSTR(a.dst, 12), a.dst) \`post\_name\`,  
 FROM\_UNIXTIME(n.changed) \`post\_modified\`,  
 n.type \`post\_type\`,  
 IF(n.status = 1, 'publish', 'private') \`post\_status\`  
 FROM drupal.node n  
 INNER JOIN drupal.node\_revisions r  
 USING(vid)  
 LEFT OUTER JOIN drupal.url\_alias a  
 ON a.src = CONCAT('node/', n.nid)  
 \# Add more Drupal content types below if applicable.  
 WHERE n.type IN ('post', 'page', 'blog')  
;

The next part sorts out all the posts in WordPress. N.B. Don’t stick pages in here, WordPress and Drupal both post/content types of *pages*. Mapping more complex post types is well outside the scope of this post, sorry.

\# Fix post type; http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/#comment-17826  
  
\# Add more Drupal content types below if applicable.  
UPDATE wordpress.wp\_posts  
 SET post\_type = 'post'  
 WHERE post\_type IN ('blog')  
;

The next chunk of code sets all the pages to pending – if the page structure is staying the same between the old and new sites, this can safely be ignored.

\# If you're keeping the same page structure from Drupal, comment out this query  
\# and the new page INSERT at the end of this script.  
UPDATE wordpress.wp\_posts SET post\_status = 'pending' WHERE post\_type = 'page';

Next up, the tags and posts that have been imported need to be connected:

\# POST/TAG RELATIONSHIPS  
INSERT INTO wordpress.wp\_term\_relationships (object\_id, term\_taxonomy\_id)  
 SELECT DISTINCT nid, tid FROM drupal.term\_node  
;  
\# Update tag counts.  
UPDATE wp\_term\_taxonomy tt  
 SET \`count\` = (  
 SELECT COUNT(tr.object\_id)  
 FROM wp\_term\_relationships tr  
 WHERE tr.term\_taxonomy\_id = tt.term\_taxonomy\_id  
 )  
;  
\# Fix taxonomy; http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/#comment-27140  
UPDATE IGNORE wordpress.wp\_term\_relationships, wordpress.wp\_term\_taxonomy  
 SET wordpress.wp\_term\_relationships.term\_taxonomy\_id = wordpress.wp\_term\_taxonomy.term\_taxonomy\_id  
 WHERE wordpress.wp\_term\_relationships.term\_taxonomy\_id = wordpress.wp\_term\_taxonomy.term\_id  
;

If the Drupal installation has content types that include image or video fields, they’ll need to be handled separately. [Scott Anderson’s SQL](http://blog.room34.com/archives/4530?ref=toodlepip.co.uk) has code that’ll handle this. Files and images that are linked into the posts are covered below.

### Comments

Next, a little more magic to transfer the comments to WordPress. Important note: with popularity comes downsides, especially comment spammers. Make sure an anti-spam tool like [Akismet](http://akismet.com/?ref=toodlepip.co.uk) is installed!

\# Keeps unapproved comments hidden.  
\# Incorporates change noted here: http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/#comment-32169  
INSERT INTO wordpress.wp\_comments  
 (comment\_post\_ID, comment\_date, comment\_content, comment\_parent, comment\_author,  
 comment\_author\_email, comment\_author\_url, comment\_approved)  
 SELECT DISTINCT  
 nid, FROM\_UNIXTIME(timestamp), comment, thread, name,  
 mail, homepage, ((status + 1) % 2)  
 FROM drupal.comments  
;  
\# Update comments count on wp\_posts table.  
UPDATE wordpress.wp\_posts  
 SET \`comment\_count\` = (  
 SELECT COUNT(\`comment\_post\_id\`)  
 FROM wordpress.wp\_comments  
 WHERE wordpress.wp\_posts.\`id\` = wordpress.wp\_comments.\`comment\_post\_id\`  
 )  
;

### Files & Images

By default, Drupal and WordPress store images in different places. Be default Drupal sticks them in a */files* directory whilst WordPress uses *wp-content/uploads* with sub-folders for years and months.

The easiest solution is to:

- Copy all the Drupal files directory into the wp-content/uploads directory, assuming there’s no conflicts
- Add an alias to your web server configuration so that the images/files still work (*see example below*)

Alias /drupal-files /path/to/wordpress-install/wp-content/drupal-files

If you don’t want to tinker with the web server and update the links in the posts/pages directly, you can use the following SQL to achieve the same effect:

\# Fix images in post content; uncomment if you're moving files from "files" to "wp-content/uploads".  
\# UPDATE wordpress.wp\_posts SET post\_content = REPLACE(post\_content, '"/files/', '"/wp-content/uploads/files/');

### Categories

Drupal’s super flexible approach to tags means this part of the migration may need some jiggery pokery which’ll have to be done by hand. WordPress stores all of its tags and categories in the same database table *wp\_terms*, then uses another table *wp\_term\_taxonomy* to define whether it’s a tag or category.

So far, the code above just dumps all of the tags out of Drupal into WordPress, it doesn’t care about how they were organised. If you’re anything like me, chances are one of the taxonomies you created was used to categories blog posts.

Now it’s time to pull out tags to use as categories. Here’s the process:

1. Manually create the categories in WordPress (can be done in SQL – see below – or using the web interface: *Admin > Posts > Categories*)
2. Update any tags that already exist, WordPress usually capitalises the first letter of a category
3. Set specific tags as categories
4. Associate blog posts and other content with these categories – this will differ significantly from site-to-site, but the basic logic is listed below

#### Manually Creating WordPress Categories

Firstly, choose which tags are going to be your categories. The SQL below will insert them, but ignore any that already exist. In this case I’m just using 3 as examples, you can add as many as you’d like:

\# Manually create categories in WordPress  
INSERT IGNORE INTO wordpress.wp\_terms (name, slug)  
 VALUES  
 ('Marketing', 'marketing'),  
 ('Social Media', 'social-media'),  
 ('Geekery', 'geekery')  
;

Remember, the slug is the name that WordPress will use when creating the URL for the categories. It mustn’t contain spaces or / characters.

#### Update Existing Tags

Let’s say we have a couple of existing tags that we’d like to turn into categories, by convention WordPress capitalises the first letter of the tag (although it’s not compulsory). This SQL tidies up the existing tags – you’ll need to edit this to include your own: 

\# Update tags from Drupal into WordPress categories  
UPDATE wordpress.wp\_terms SET name = 'Internal Communications' WHERE name = 'internal comms';  
UPDATE wordpress.wp\_terms SET name = 'Extreme Baking' WHERE name = 'extreme baking';  
UPDATE wordpress.wp\_terms SET name = 'Rock & Roll Accountancy' WHERE name = 'grooving numbers';

#### Turn Tags Into Categories

Next, we need to set these as categories in WordPress. The SQL below uses the slug (the machine readable name, remember no spaces or /) and includes the new categories and the ones we’ve just updated: 

\# Add categories to taxonomy in WordPress  
INSERT INTO wordpress.wp\_term\_taxonomy (term\_id, taxonomy)  
 VALUES  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'marketing'), 'category'),  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'social-media'), 'category'),  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'geekery'), 'category'),  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'internal-comms'), 'category'),  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'extreme-baking'), 'category'),  
 ((SELECT term\_id FROM wp\_terms WHERE slug = 'grooving-numbers'), 'category')  
;

#### Add Content to Categories

And now the tricky part – assigning the imported content to those categories. 

The SQL uses two pieces of logic to handle this:

1. content contains a specific word or phrase
2. and/or that it was tagged in Drupal with one or more terms

\# Auto-assign posts to category.  
\# You'll need to work out your own logic to determine strings/terms to match.  
\# Repeat this block as needed for each category you're creating.  
INSERT IGNORE INTO wordpress.wp\_term\_relationships (object\_id, term\_taxonomy\_id)  
 SELECT DISTINCT p.ID AS object\_id,  
 (SELECT tt.term\_taxonomy\_id  
 FROM wordpress.wp\_term\_taxonomy tt  
 INNER JOIN wordpress.wp\_terms t USING (term\_id)  
 WHERE t.slug = 'enter-category-slug-here'  
 AND tt.taxonomy = 'category') AS term\_taxonomy\_id  
 FROM wordpress.wp\_posts p  
 WHERE p.post\_content LIKE '%enter string to match here%'  
 OR p.ID IN (  
 SELECT tr.object\_id  
 FROM wordpress.wp\_term\_taxonomy tt  
 INNER JOIN wordpress.wp\_terms t USING (term\_id)  
 INNER JOIN wordpress.wp\_term\_relationships tr USING (term\_taxonomy\_id)  
 WHERE t.slug IN ('enter','terms','to','match','here')  
 AND tt.taxonomy = 'post\_tag'  
 )  
;

Pay paticular attention to the highlighted lines (9, 12, 18):

- Line 9 – add the WordPress slug for the category defined above e.g. *geekery*
- Line 12 – change to any text you’d like to match from the posts e.g. *Drupal setup*. N.B. This can also be set to *‘%%’* which will match everything
- Line 18 – one or more Drupal tags to match e.g. if we used geekery in the original Drupal site, add ‘geekery’ here as a tag, remember this is the tag from Drupal not WordPress

Here’s the generic code from above modified with those settings:

\# Auto-assign posts to category.  
\# You'll need to work out your own logic to determine strings/terms to match.  
\# Repeat this block as needed for each category you're creating.  
INSERT IGNORE INTO wordpress.wp\_term\_relationships (object\_id, term\_taxonomy\_id)  
 SELECT DISTINCT p.ID AS object\_id,  
 (SELECT tt.term\_taxonomy\_id  
 FROM wordpress.wp\_term\_taxonomy tt  
 INNER JOIN wordpress.wp\_terms t USING (term\_id)  
 WHERE t.slug = 'geekery'  
 AND tt.taxonomy = 'category') AS term\_taxonomy\_id  
 FROM wordpress.wp\_posts p  
 WHERE p.post\_content LIKE '%Drupal setup%'  
 OR p.ID IN (  
 SELECT tr.object\_id  
 FROM wordpress.wp\_term\_taxonomy tt  
 INNER JOIN wordpress.wp\_terms t USING (term\_id)  
 INNER JOIN wordpress.wp\_term\_relationships tr USING (term\_taxonomy\_id)  
 WHERE t.slug IN ('geekery')  
 AND tt.taxonomy = 'post\_tag'  
 )  
;

This will need to be repeated for each of the categories created in WordPress. 

**WARNING**: This is pretty hard to undo if you get the logic wrong – I ended up starting over twice until I realised you can easily test your logic by deleting the first line (starting *INSERT*) and running the SQL. This’ll show which posts will be affected.

Finally for the tags, the category counts need to be updated.

\# Update category counts.  
UPDATE wp\_term\_taxonomy tt  
 SET \`count\` = (  
 SELECT COUNT(tr.object\_id)  
 FROM wp\_term\_relationships tr  
 WHERE tr.term\_taxonomy\_id = tt.term\_taxonomy\_id  
 )  
;

And breathe. The tags are probably the fiddliest bit of the whole transfer.

### Creating Redirect for Old Posts to New URLs

WordPress and Drupal use different patterns for permalinks and part of the migration process will have altered the permalinks that WordPress uses to show the posts. Fortunately the node id from Drupal is also the post id in WordPress.

#### Redirects for Posts

Using a bit of SQL, we can build a list of redirects that can be added to the web server configuration. For search engine optimisation this is important otherwise all the work done getting your posts good profile on Google will start again from scratch.

First, make sure that no / are left in the URLs otherwise they won’t show up in WordPress at all. This *should* have been covered in the main import of posts above, but a few slipped through when I tried it.

\# Double check all slashes are removed from permalinks  
UPDATE wordpress.wp\_posts  
 SET post\_name = REPLACE(post\_name, ‘/’, ‘-’);

The SQL below builds the redirect statements which’ll then need to be added to the web server configuration:

\# Create Apache redirects  
SELECT CONCAT ("Redirect permanent /", dst, " /?p=", RIGHT(src, LENGTH(src)-5))   
 FROM \`url\_alias\`  
WHERE  
 src REGEXP '^node'  
 AND  
 RIGHT(src, LENGTH(src)-5) IN (  
 SELECT nid FROM node WHERE type IN ('story', 'blog')  
 );

The Drupal content types can be updated or changed by altering line 8 in the code above.

#### Redirects for Tags

Tags in Drupal use – as a separator for spaces, WordPress uses \_, so we’ll also need to create a bunch of redirects for the tag pages. This may need to be adjusted depending on the structure of the Drupal site. 

In this instance:

- old site was: /tags/tag-name
- new site should be: /tag/tag\_name
This SQL will do the magic: 

\# Create Apache redirects for tags  
SELECT DISTINCT  
 CONCAT("Redirect permanent /tags/",  
 REPLACE(LOWER(d.name), ' ', '-'),  
 " /tag/",  
 REPLACE(LOWER(d.name), ' ', '\_'))  
 FROM drupal.term\_data d  
 INNER JOIN drupal.term\_hierarchy h  
 USING(tid)  
 INNER JOIN drupal.term\_node n  
 USING(tid)  
;

If you have different URL structures for your tag links, adjust lines 3 and 5 accordingly.

The lines generated from the two SQL statements above can be added to the top of the .htaccess file and bingo, the search engines should pick start picking up the new links and indexing them. Job done…That wasn’t so bad, was it? OK, it’s pretty industrial but hopefully everything worked out OK. N ow it’s time to add some plugins to make it work better! 

Photo (cc) [zman z28](http://www.flickr.com/people/39158515@N05/?ref=toodlepip.co.uk) on [Flickr](http://www.flickr.com/photos/39158515@N05/4001707143/in/photolist-76BN7K-78pdHX-79zpD6-dCkJqE-dPoze4-846AeR-dCXu4q-dMG3qL-9o7SKF-aYKPnv-8CHb52-hKNaHU-8RKE3t-8CHbNV-ioqVKn-8JAbwK-bptveZ-fcDGTc-bFJZUz-9tfCVa-8X2X3Q-8SeDfs-86ncE7-8uvTtM-c9uN7h-7y8W4u-bCfaPZ-81JA6w-81JzLb-bHNHGZ-bHNJVp-8N6Lgb-dVeuBD-ae7Fan-8RNEqs-a8RKt6-d2cvkj-fNHt5X-buTFF1-a5mnmy-d9GqDE-bojGgC-dHKJVe-dHQ8Kh-dHQ92u-dHR9NS-dHKJmv-dHKJ1z-8b8Art-az2qgE-bjFmtq/?ref=toodlepip.co.uk).

---

### Comments from the original blog

*These comments were left on the original WordPress version of this post and are kept for reference. Comments are closed.*

**Sally** · 17 January 2014

What about the traffic and ranking? Does it support 301 redirect? I have found a plugin wordpress.org It seems to be easy. But has anyone used it?

**Sam Michel (author)** · 20 January 2014

Hi Sally – there’s a section in the blog post that covers Creating Redirect for Old Posts to New URLs that’ll generate a bunch of redirects that can be added to the .htaccess file or your webserver. It uses 301 redirects so should maintain your SEO ranking.

Thanks for pointing out that plugin, looks like it didn’t exist when I did the migration, but will definitely keep an eye on it for the future. Looks like that firm offer a paid-for option, too. There’s a few out there, which I’d definitely look at for a more complex site. Good luck with the migration.

**Jean-Philippe** · 3 December 2014

Hello, I’m having a tough time with my migration seeing as the Drupal tables you’re referring to don’t exist in my database (term\_data ; term\_hierarchy ; term\_node).

I’ve guessed that term\_data refers to taxonomy\_term\_data and term\_hierarchy refers to taxonomy\_term\_hierarchy but I still don’t know about term\_node. In various forums I’ve seen it mentioned as being part of term\_menu (but I don’t have that one either). I’m quite certain it isn’t the node table.

This website never had its taxonomy corrected and none of the previous admins knew anything about php, so I assume the tables remained untouched.

**Sam Michel (author)** · 10 December 2014

Hi Jean-Philippe – not sure about your question. Are you definitely on Drupal 6? If your site didn’t have a taxonomy defined, perhaps those tables weren’t created? I’d run a test migration and see what’s affected. I ended up doing a little bit of tinkering with the taxonomies to make them work properly on WordPress.

**kerfred** · 19 September 2016

Great tutorial.

But it is a heavy work to do it manually.

Instead you can use a migrator tool like FG Drupal to WordPress that can move all the Drupal data to WordPress automatically. It is compatible with Drupal 6, Drupal 7 and Drupal 8.

fredericgilles.net

**Sam Michel (author)** · 19 September 2016

Thanks, I’ll take a look. Just about to do another migration and would love an automated solution.

**slyvershyne** · 29 December 2016

Hi Sam, I must say this is a total life saviour. Thank you very much for all this, very very much 🙂 Have a great day 🙂 🙂