Easy Ways to Migrate Posts from One Site to Another
In this article, I want to show you how to transfer multiple WordPress posts between sites (and you can choose manually which posts).
More than that, I want it to be almost a one-click solution, something like this:

What you can see in the screenshot above:
- You can connect target sites only once and then migrate posts there at any time.
- You can select only specific posts and pages if you need, or select all of them.
- If a post has already been transferred from “Site A” to “Site B”, then you can sync the changes made in the original version of the post at any time as well.
Seems quite convenient, doesn’t it?
Of course, there are tons of export and import plugins that are available to you; however, doing it using the export-import method is too much trouble, in my opinion. It may only do the trick during the initial migration of all posts.
Let’s start with the moment that the process of copying posts from one site to another can be different, depending on the WordPress setup you’re using – whether you have two completely standalone sites or when your sites are a part of a single WordPress multisite network.
We will discuss both setups below.
Transferring Posts Between Standalone WordPress Sites
I want to start with standalone sites, because, in my opinion, this setup is more widely used. So, I am going to show you both the programmatic and the plugin way to do it.
How to do it easily using a plugin?
In this chapter, we will use a plugin to transfer posts from one WordPress site to another.
Please, install Simple WP Crossposting first, and then continue reading this chapter.
The whole process here consists of two small steps. First, you need to connect one or multiple target sites in the plugin settings, Settings > Crosspost > Sites:

Once you’ve done that, you can start selecting posts and transferring them to a selected site:

Syncing posts between standalone sites programmatically
Now, let’s talk about the programmatic approach. First things first, we need to add a new custom option to bulk actions. It can be done easily with both bulk_actions-edit-{post type} and handle_bulk_actions-edit-{post type} hooks.
add_filter( 'bulk_actions-edit-post', 'rudr_bulk_option' );
add_filter( 'handle_bulk_actions-edit-post', 'rudr_do_bulk_action', 10, 3 );If you have plans about using it for multiple post types, please consider using a loop like this:
$post_types = array( 'post', 'page', 'misha_custom_post_type' );
foreach( $post_types as $post_type ) {
add_filter( 'bulk_actions-edit-' . $post_type, 'rudr_bulk_option' );
add_filter( 'handle_bulk_actions-edit-' . $post_type, 'rudr_do_bulk_action', 10, 3 );
}We have two callback functions so far:
rudr_bulk_option()– adds a custom bulk action into a list of bulk actions,rudr_do_bulk_action()– processes it (to bulk publish posts to the other sites, in other words).
Let’s begin with the first callback function, which is the simplest one:
function rudr_bulk_option( $bulk_actions ) {
$bulk_actions[ 'publish_to_other_site' ] = 'Publish to the other site';
return $bulk_actions;
}Yes, it is super simple; even after using this code, we already have something:

Of course, if you select this new bulk action and try to execute it, nothing will happen. And actually, the main part of the code is below:
function rudr_do_bulk_action( $redirect, $doaction, $object_ids ) {
// exit the function if it is not our custom bulk action
if( 'publish_to_other_site' !== $doaction ) {
return $redirect;
}
// all the batch requests are going to be in this array
$requests = array();
foreach( $object_ids as $object_id ) {
$post = get_post( $object_id );
// add a part to our batch request
$requests[] = array(
'method' => 'POST',
'path' => '/wp/v2/posts',
'body' => array(
'status' => $post->post_status,
'title' => $post->post_title,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
),
);
}
$request = wp_remote_post(
'https://your-site-url/wp-json/batch/v1/',
array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$username}:{$pwd}" ),
'Content-Type' => 'application/json; charset=utf-8',
),
'body' => json_encode( array(
'requests' => $requests,
) )
)
);
if( 207 === wp_remote_retrieve_response_code( $request ) ) {
$redirect = add_query_arg(
array(
'sync_done' => count( $object_ids ),
),
$redirect
);
}
return $redirect;
}Let me talk a little bit more about this piece of code
- I only provided some basic post data like post title, content, excerpt, and status. But you will probably want to sync at least custom fields and featured images. I will not lie, it is not an easy task, that’s why I decided to skip it in this part of the tutorial. If you don’t want to deal with it, you can always use the plugin approach that covers everything.
- When everything goes well, the REST API batch request returns a
207status code. If some posts haven’t been published for some reason, it doesn’t affect the batch API request, but you can check for errors inside the body of each sub-request. - Last but not least, if you don’t know where to get
$usernameand$pwdvalues, please read about application passwords. - One more thing, the code above will work great only when publishing posts to “Site B”, but if a specific post has already been published and you want to sync the new changes made on “Site A”, then it will not work, because we need to provide a target post ID, and also to change the request method from
POSTtoPUT.
And of course, let’s not forget about admin notices:
add_action( 'admin_notices', 'rudr_notices' );
function rudr_notices() {
// do nothing if we're on other admin pages
$screen = get_current_screen();
if( 'edit' !== $screen->base ) {
return;
}
if( ! empty( $_REQUEST[ 'sync_done' ] ) ) {
printf( '<div class="updated notice is-dismissible"><p>' . _n( '%s post has been successfully published.', '%s posts have been successfully published.', absint( $_REQUEST[ 'sync_done' ] ) ) . '</p></div>', absint( $_REQUEST[ 'sync_done' ] ) );
}
}Here you go, the final result:

One more thing, by default, the WordPress REST API allows us to use up to 25 requests within a single batch, but guess what, you can change it with the help of a filter hook rest_get_max_batch_size.
add_filter( 'rest_get_max_batch_size', 'rudr_max_batch_size' );
function rudr_max_batch_size() {
return 50;
}That’s pretty much it for the programmatic approach for transferring posts from one WordPress site to another.
Transferring Posts Between Subsites in a Multisite Network
In case your subsites are a part of a single WordPress Multisite network, the process will be a little bit different:
- We will use Simple Multisite Crossposting instead of Simple WP Crossposting.
- Copying posts to another subsite is as easy as using two WordPress functions –
switch_to_blog()andwp_insert_post(). No need to dive into the REST API requests.
Let’s dive into it now.
Syncing (transferring) posts between subsites using a plugin
Again, I will show you a simpler way first – using a plugin.
You need to install and network-activate the Simple Multisite Crossposting plugin first.
First things first, in the plugin settings, make sure the target subsite is allowed for crossposting:

The synchronization UI is a little bit different (compared to standalone sites). I decided it would be nice to tap into the standard WordPress bulk edit functionality.

Programmatically
The programmatic approach here is pretty much the same as we used above when doing it for standalone WordPress sites.
Everything starts with creating a bulk action:
add_filter( 'bulk_actions-edit-post', function( $bulk_actions ) {
$bulk_actions[ 'publish_to_other_subsite' ] = 'Publish to the other subsite';
return $bulk_actions;
} );Only after using this code, we will already have this:

The only thing to keep in mind is that your code must be activated only for the initial subsite of a WordPress multisite network, let’s say, it is “Subsite A”.
Now, it is time to process the bulk action, and it will be different from what we did before.
add_filter( 'handle_bulk_actions-edit-post', function( $redirect, $doaction, $ids ){
if( 'publish_to_other_subsite' !== $doaction ) {
return $redirect;
}
$posts = array();
$target_subsite_id = 2;
foreach( $ids as $post_id ) {
$post = get_post( $post_id );
if( ! $post ) {
continue;
}
$post_data = array(
'post_title' => $post->post_title,
'post_content' => $post->post_content,
'post_excerpt' => $post->post_excerpt,
'post_status' => $post->post_status,
'post_type' => 'post',
);
$posts[] = $post_data;
}
// switch to the target subsite
switch_to_blog( $target_subsite_id );
foreach( $posts as $post_data ) {
wp_insert_post( $post_data );
}
restore_current_blog();
return add_query_arg(
array(
'sync_done' => count( $ids ),
),
$redirect
);
}, 10, 3 );If you have any questions related to my plugins or the code, feel free to ask in the comments.
Misha Rudrastyh
Hey guys and welcome to my website. For more than 15 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
I have three sites A, B and C
– User case 1: How can I stand at site C and post to both of A and B?
– User case 2: How can I stand at site C and choose which site can be post (A or B)?
Thank much you
You just need to select the sites you need from the dropdown (or using the checkboxes, if you have a WordPress Multisite network).