After hours or days of failing to move my website from one host to another, this is how I did it.

It involves buying a plugin – which I would rather not. But calculate the value of your time and using these plugins is an absolute no brainer. No affiliate links here. This post is mostly so I can follow the steps again if I need to.

  1. Go and buy WP All Import. The benefit of this is that (at time of writing) there’s a one off purchase. Everyone else seems to have moved to recurring fees. You do need to buy all the add on plugins too. That feels cynical but again even if you think you’re only worth £10 an hour this will pay for itself!
  2. Grab a list of all the plugins that you’re currently running. I do a copy and paste into Excel for this. It’s good to know what you need to work through.
  3. Install WP All Export and the Users and WooCommerce add-ons to the current site.
  4. Download the things you want. For me it is:
    • Posts
    • Pages
    • Users (Woo Customers for me as I’m not interested in the bogus users I’ve collected along the way)
    • Products
    • Coupons
    • Orders
    • Contact Forms
    • WC Custom messages
  5. Once you’ve selected and chosen to export you can save it to your PC. Do this as a ‘Bundle’ and the mapping is pre-configured when you come to import.
  6. If you’ve got a busy shop do this out of hours and put the site into maintenance mode before exporting users/customers and orders. I use the free WP Maintenance Mode LightStart. Nice and straightforward.
  7. Now, spin up the new server – ideally with the same WordPress version.
  8. To save all sorts of initial headaches it is easiest to do this with the original domain still running – that is because the images can be downloaded from the URL on import. If not you need to be able to FTP images to wp-import/files and map them that way. I transferred the domain by mistake and ended up with a nightmare of not being able to upload images to the products – it kept telling me that it couldn’t move the file to the location wp-content/uploads/* and a folder of the year that the product was first created. I’m now re-importing everything, having failed to solve the problem!
  9. The first thing I then install is maintenance mode again. Don’t want people finding the raw WordPress by mistake.
  10. Now install plugins. Keep them to a minimum to start with:
    • Woocommerce
    • Paypal
    • Payment gateway (Square for me)
    • Facebook for Woocommerce
    • Any abandoned cart or CRM plugins that might be hooked to customers or orders (for me Retainful)
  11. Activate WooCommerce. Then set up with the basics (store location, currency and tax options).
  12. Next, activate the other associated Woo plugins. I don’t worry about setting the up now, it’s just to get the meta keys up and running
  13. Now we need to upload and activate the WP All Import plugin and then the add-ons
  14. Now I want to preserve the IDs, there’s some code to put into WP Import > Settings
  15. Scroll to the bottom for Functions Editor and add this:
// this is to keep the old IDs on importing posts (products, orders, coupons)

function my_pmxi_article_data( $articleData, $import, $post_to_update, $current_xml_node ) {
    // Turn the XML node into an array.
    $xml_data = json_decode( json_encode( (array) $current_xml_node ), 1 );

    // Change 'id' if your ID exists in an element not named {id[1]}.
    $articleData['import_id'] = $xml_data['id'];

    // Return article data.
    return $articleData;
}
add_filter('pmxi_article_data', 'my_pmxi_article_data', 10, 4);


function my_set_user_id( $post_id, $xml_node, $is_update ) {

	global $wpdb;

	// Retrieve the import ID.
	$import_id = wp_all_import_get_import_id();

	// Only run for import 1 and only run when the user is first created.
	if ( $import_id == '1' && !$is_update) {

		// Convert SimpleXml object to array for easier use.
		$record = json_decode( json_encode( ( array ) $xml_node ), 1 );

		// ID to set for user, change 'userid' to your real file element name.
		$requested_id = $record['userid'];

		// Check if the requested ID is already used.
		$id_exists = $wpdb->get_var($wpdb->prepare('SELECT user_login FROM '.$wpdb->users.' WHERE ID = '.$requested_id));

		// If the requested ID is available...
		if( $id_exists === null ){

			// ...assign the user ID as desired...
			$wpdb->update($wpdb->users, ['ID'=> $requested_id], ['ID' => $post_id]);

			// ...update the user ID for the associated meta, so the association with the user isn't lost...
			$wpdb->update($wpdb->usermeta, ['user_id'=> $requested_id], ['user_id' => $post_id]);

			// ...and update the ID in the pmxi_posts table, so the import can still manage this user.
			$wpdb->update($wpdb->prefix . 'pmxi_posts', ['post_id'=> $requested_id], ['post_id' => $post_id, 'import_id' => $import_id]);
		}

	}

}
add_action( 'pmxi_saved_post', 'my_set_user_id', 10, 3 );

And click Save functions

16. Now, there’s an order to follow to make sure that the orders match to customers and contain products. So we go:

1st Products

2nd Customers/users

3rd Coupons

4th Orders

17. Go to WP Import > New Import and select the Products export file. Follow the steps. You shouldn’t need to do anything if you had the bundle download. With the function code you will also see the the id of your products are taken from the previous site. This is what will link your products to your orders.

18. Next WP Import > New Import and choose the customers file. Again, ID will be preserved thanks to the code in the Functions

19. Next, WP Import > New Import for the coupons. With this import, we need to make a change to match the ID of the file in the import process. In step 3, scroll to and open the Function Editor and amend the id reference to couponid:

$articleData['import_id'] = $xml_data['couponid'];

Save the functions and then run the import.

20. Now we’re ready for the orders. WP Import > New Import and find the file for the exported orders. When I set up my shop I didn’t have SKUs from my products. This causes a problem when importing, because there is nothing to match in that field. A real bind! So, in the Order Details section I have to add the details manually. I guess that the number you have will vary according to the maximum number of products that a customer has bought. In my case 14.

So Product unique key I match to productid:

{productid1[1]}|{productid2[1]}|{productid3[1]}|{productid4[1]}|{productid5[1]}|{productid6[1]}|{productid7[1]}|{productid8[1]}|{productid9[1]}|{productid10[1]}|{productid11[1]}|{productid12[1]}|{productid13[1]}|{productid14[1]}

Product Name:

{productname1[1]}|{productname2[1]}|{productname3[1]}|{productname4[1]}|{productname5[1]}|{productname6[1]}|{productname7[1]}|{productname8[1]}|{productname9[1]}|{productname10[1]}|{productname11[1]}|{productname12[1]}|{productname13[1]}|{productname14[1]}

Then under add More Product Meta, Price per Unit:

{itemcost1[1]}|{itemcost2[1]}|{itemcost3[1]}|{itemcost4[1]}|{itemcost5[1]}|{itemcost6[1]}|{itemcost7[1]}|{itemcost8[1]}|{itemcost9[1]}|{itemcost10[1]}|{itemcost11[1]}|{itemcost12[1]}|{itemcost13[1]}|{itemcost14[1]}

Quantity:

{quantity1[1]}|{quantity2[1]}|{quantity3[1]}|{quantity4[1]}|{quantity5]}|{quantity6[1]}|{quantity7[1]}|{quantity8[1]}|{quantity9[1]}|{quantity10[1]}|{quantity11[1]}|{quantity12[1]}|{quantity13[1]}|{quantity14[1]}

With this import, we need to make a change to match the ID of the file in the import process. In step 3, scroll to and open the Function Editor and amend the id reference to orderid:

$articleData['import_id'] = $xml_data['orderid'];

As a belt and braces, I also create and new custom field called “_old_order_id” and drag ‘orderid’ from the field picker to the value space or you could type in: {orderid[1]} We could then call that in a reference later, if needed.

Don’t forget to save the functions and then run the import. This one can take quite a while.

21. Now import the remaining content – it’s less important about the order and, I guess, whether the IDs are preserved. But, we have them so why not! Just be sure to change the ID reference in function editor.