Moving from Expression Engine 1 to WordPress

NewTheWineDarkSea-3

Earlier this year I moved my blog from the content management system Expression Engine (the older 1.x, not the newer 2.x version) to WordPress. I had been planning to do the same for Melanie’s blog this summer but didn’t get around to it until forced by circumstances.

Now that I’ve done it twice, I wanted to post my notes on the process for anyone who might follow in my footsteps, especially if they encounter the same problems I did and go searching for answers. Perhaps I might save someone the days of pounding my head against the wall.[1]

Much of the hard work involved here was done by the blogger at CodeGeek, but as English is not his first language and there were some steps that could use some clarification, I will reiterate here. Plus I will include a few additional steps that made life easier.

First, download the entirety of your original Expression Engine database from your website host. (I won’t go into details as the exact steps will vary, but it’s likely you will be accessing a webpage through your host in a program call phpmyadmin.) You won’t be using this file to do the transfer. It’s just so you have a good backup of your site at the moment of conversion.

Second, if you’re moving from one webhost to another, download your images folder for later uploading to the new site.

Third, create the “export” template group as described by Musa at CodeGeek, including the two xml files, “data.xml” and “comments.xml”.

This is the content of data.xml:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
    xmlns:excerpt="http://wordpress.org/export/1.0/excerpt/"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:wp="http://wordpress.org/export/1.0/"
>
<channel>
    <title>{site_name}</title>
    <link>{site_url}</link>
    <description>SITE_DESCRIPTION_HERE</description>
    <pubDate>{current_time format="{DATE_RSS}"}</pubDate>
    <generator>http://wordpress.org/?v=2.8.6</generator>
    <language>en</language>
    <wp:wxr_version>1.0</wp:wxr_version>
    <wp:base_site_url>{homepage}</wp:base_site_url>
    <wp:base_blog_url>{site_url}</wp:base_blog_url>

{exp:weblog:categories style="linear" weblog="default_site"}

    <wp:category>
        <wp:category_nicename>{category_url_title}</wp:category_nicename>
        <wp:category_parent></wp:category_parent>
        <wp:cat_name><![CDATA[{category_name}]]></wp:cat_name>
    </wp:category>

{/exp:weblog:categories}    

{exp:weblog:entries weblog="default_site" orderby="date" sort="desc" limit="200" offset="0" rdf="off"}
        <item>
        <title>{exp:xml_encode}{title}{/exp:xml_encode}</title>
        <link>YOUR_BLOG_URL_WITH_TRAILING_SLASH?p={entry_id}</link>
        <pubDate>{entry_date format="{DATE_RSS}"}</pubDate>
        <dc:creator><![CDATA[{author}]]></dc:creator>

                {categories}
                <category><![CDATA[{category_name}]]></category>
                <category domain="category" nicename="{category_url_title}"><![CDATA[{category_name}]]></category>
                {/categories}

        <guid isPermaLink="false">YOUR_BLOG_URL_WITH_TRAILING_SLASH?p={entry_id}</guid>
        <description></description>
        <content:encoded><![CDATA[{body}]]></content:encoded>
        <excerpt:encoded><![CDATA[{summary}]]></excerpt:encoded>
        <wp:post_id>{entry_id}</wp:post_id>
        <wp:post_date>{entry_date format="%Y-%m-%d %H:%i:%s"}</wp:post_date>
        <wp:post_date_gmt>{gmt_entry_date format="%Y-%m-%d %H:%i:%s"}</wp:post_date_gmt>
        <wp:comment_status>open</wp:comment_status>
        <wp:ping_status>open</wp:ping_status>
        <wp:post_name>{url_title}</wp:post_name>
        <wp:status>publish</wp:status>
        <wp:post_parent>0</wp:post_parent>
        <wp:menu_order>0</wp:menu_order>
        <wp:post_type>post</wp:post_type>
        <wp:post_password></wp:post_password>

        <wp:postmeta>
            <wp:meta_key>extended</wp:meta_key>
            <wp:meta_value><![CDATA[{extended}]]></wp:meta_value>
        </wp:postmeta>

        {embed="export/comments.xml" the_entry_id="{entry_id}"}         

        </item>
{/exp:weblog:entries}   

</channel>    
</rss>

This is comments.xml:

    <?php

global $IN;
$IN->QSTR = '{embed:the_entry_id}';

?>

{exp:comment:entries weblog="default_site" sort="asc"}

<wp:comment>
<wp:comment_id>{comment_id}</wp:comment_id>
<wp:comment_author><![CDATA[{name}]]></wp:comment_author>
<wp:comment_author_email>{url_or_email}</wp:comment_author_email>
<wp:comment_author_url>{url}</wp:comment_author_url>
<wp:comment_author_IP>{ip_address}</wp:comment_author_IP>
<wp:comment_date>{comment_date format="%Y-%m-%d %h:%i:%s"}</wp:comment_date>
<wp:comment_date_gmt>{gmt_comment_date format="%Y-%m-%d %h:%i:%s"}</wp:comment_date_gmt>
<wp:comment_content><![CDATA[{comment}]]></wp:comment_content>
<wp:comment_approved>1</wp:comment_approved>
<wp:comment_type></wp:comment_type>
<wp:comment_parent>0</wp:comment_parent>
<wp:comment_user_id>0</wp:comment_user_id>
</wp:comment>

{/exp:comment:entries}

This is very important! After you create comments.xml, you must enable PHP parsing in the template or you will never be able to export comments.

title

title

You’ll need to edit a few places in the files:

  • Where it says SITE_DESCRIPTION_HERE put the tagline for your site
  • Where you see YOUR_BLOG_URL_WITH_TRAILING_SLASH, put the URL to where your WordPress install will go. For example, it might http://www.site.com/blog
  • Where it says weblog=“default_site”, replace default_site with the actual weblog shortname you have for your blog.

Now open up your website at the data.xml site. For example, in your web browser, go to http://www.site.com/blog/data.xml.

This will bring up the most recent 200 entries on your blog, along with their comments. Save this file to your hard drive with a unique name ending in 1.xml. For example, blogexport1.xml.

Go back into the data.xml template, look for this line:

{exp:weblog:entries weblog="weblog1" orderby="date" sort="desc" limit="200" offset="0" rdf="off"}

Change the offset to “200” and then reload the page http://www.site.com/blog/data.xml. It will now show the next 200 entries with their comments. Save this with the same name as before incremented by 1. For example, blogexport2.xml.

Keep doing this in 200 entry increments, i.e. offset=“400”, offset=“600”, and so on, until you have downloaded all of your entries.

Why not just download everything in one fell swoop? Because your server will likely timeout and/or choke on the data. This is an intensive process so you have to take it in smaller chunks. I’ve tried with increments of 400, but if there are a lot of comments on a post or some very long posts, it will fail.

Potential pitfalls

For some reason the xml files can be malformed as they generate. Occasionally they encounter some data in your blog that causes it to choke and the file will end abruptly. You’ll know this happened if the file you’re saving doesn’t end properly with

</channel>    
</rss>

You’ll have to track down the offending post and edit it until the process runs smoothly,

Another potential problem is that comments get attached to the wrong post. This happened with both my and Melanie’s blogs in that for some of our older posts the comments ended up attached to the post before or after the one they’re supposed to. I don’t know why and I couldn’t figure out a fix. If you can’t either, you’ll just have to live with it, I guess.

Importing

After all the entries have been exported from Expression Engine, set up your new WordPress site.[2] If you’re moving your domain to a new webhost, go through that process. Make sure your images/uploads folder from your old site is in the same directory structure in the new site and move all the images over so your blog posts won’t have broken image links. (E.g. /site.com/images/uploads).

Then in WordPress itself, go to “Tools>Import” and click on “WordPress” (It will probably need to install the importer) and select the first export file. Go through the process, assign the authors to the proper user on the new WordPress site and once it’s done, verify that everything came over properly. If not, you can use the WordPress plugin Bulk Delete to delete everything you just imported so you can fix whatever went wrong and start again.

Now if you’ve been blogging a while, you have a lot of Google-fu built up to your old Expression Engine permalinks, not to mention all those links from your fellow bloggers to your old content. At this point, that’s all broken, but we can save it with a little editing of the .htaccess file at the root of your site installation.[3]

Add the following to your .htaccess:

RewriteEngine on
RewriteRule ^YOUR_SITE.com/([a-zA-Z0-9_-]+)$ YOUR_SITE/weblog/comments/$1 [R=301,NC,L]

Replace YOUR_SITE.com with your domain name and make sure that the default template group on your EE blog was /weblog/comments, otherwise you’ll need to change that to reflect it.

Finally, you won’t want to orphan all those people who followed your site via RSS so you’ll need to let them know that it’s changed. Create a new file at the root of your site called rss.xml. Put the following in it, editing it of course to include your details:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">

<channel>
    <title>YOUR_SITE.com</title>
    <link>http://www.YOUR_SITE.com</link>
    <description>My Blog</description>
    <item>
            <title>YOUR_SITE.com has moved</title>
            <link>http://www.YOUR_HOST.com/</link>
            <description>My blog host has changed and thus the feed required to subscribe to these posts has changed. Please visit http://www.YOUR_SITE.com to subscribe to the new feed. Thank you. </description>
    </item>
</channel>

</rss>

Then in .htaccess add the following line:

Redirect 301 /index.php/weblog/rss_2.0/ /rss.xml

This will now show one final entry in your old RSS feed that will hopefully lead to people coming to your site and re-subscribing with the new feed that WordPress includes (or the one provided by a third-party service like FeedBurner, if that’s what you choose to use.)

All told, it’s a relatively straightforward process, once all the steps are known and mapped out. It’s just a bit time-consuming if you’re blog’s been around as long as ours have–Melanie had about 2,800 entries spanning 7 years, my blog had many more spanning about 12 years–but even so I did the switch in a weekend, including setting up the new WordPress theme.

If you have any further tips or encounter any unforeseen problems, please leave me a comment or drop a note.


  1. Unfortunately, the makers of Expression Engine no longer support the 1.x version of their software, having moved to 2.x several years ago. As the software had evolved into a more robust commercial website product rather than the more focused blogging system it had been, I hadn’t wanted to spend the money to upgrade. Unfortunately, the process outlined here to move to WordPress hadn’t been developed at the time so I was stuck in a limbo until I discovered this.  ↩
  2. This includes creating or installing the new theme, which you won’t be able to just import from Expression Engine. That’s a whole other discussion.  ↩
  3. Explaining what .htaccess is goes beyond this little tutorial. Be aware that if you mess up this file, you can make your site completely inaccessible. On the other hand, if you’re careful, it’s very useful. You’ll generally need to edit the file after accessing your site using an FTP program.  ↩

Domenico Bettinelli, Jr., is a father of five and husband, a Roman Catholic, born in Boston, educated at Franciscan University of Steubenville, who has worked in Catholic media--print, broadcast, and online--since the mid-90s. Find out all about Dom on his About Me page. He is also the CEO of the StarQuest Production Network at sqpn.com. All opinions on this site are solely those of Domenico Bettinelli and do not reflect the opinions of anyone else. See the disclaimer for further details.