Copy Pages between Sites in Multisite Network
In this tutorial I will show you an easy way to duplicate WordPress pages to any of your sites within a multisite network.
There are different ways to implement that but we going to use WordPress bulk actions.

And of course, if you don’t want to code, I can also recommend an even better solution, it is my plugin developed specifically to copy posts and pages between sites.
The ready code for your theme functions.php
:
/**
* WordPress Multisite: Copy or Move Pages between Sites
*
* @author Misha Rudrastyh
* @link https://rudrastyh.com/wordpress-multisite/move-posts-between-blogs.html
*/
// add bulk actions
add_filter( 'bulk_actions-edit-page', 'rudr_my_bulk_multisite_actions' );
function rudr_my_bulk_multisite_actions( $bulk_array ) {
$sites = get_sites( array(
// 'site__in' => array( 1,2,3 )
'site__not_in' => get_current_blog_id(), // excluding current blog
'number' => 50,
) );
if( $sites ) {
foreach( $sites as $site ) {
$bulk_array[ 'move_to_' . $site->blog_id ] = 'Move to "' . $site->blogname . '"';
}
}
return $bulk_array;
}
// move or Copy posts
add_filter( 'handle_bulk_actions-edit-page', 'rudr_bulk_action_multisite_handler', 10, 3 );
function rudr_bulk_action_multisite_handler( $redirect, $doaction, $object_ids ) {
// we need query args to display correct admin notices
$redirect = remove_query_arg( array( 'misha_posts_moved', 'misha_blogid' ), $redirect );
// our actions begin with "move_to_", so let's check if it is a target action
if( strpos( $doaction, 'move_to_' ) === 0 ) {
$blog_id = str_replace( 'move_to_', '', $doaction ); // get blog ID from action name
foreach ( $object_ids as $post_id ) {
// get the original post object as an array
$post = get_post( $post_id, ARRAY_A );
// if you need to apply terms (more info below the code)
$post_terms = wp_get_object_terms( $post_id, 'category', array( 'fields' => 'slugs' ) );
// get all the post meta
$data = get_post_custom( $post_id );
// empty ID field, to tell WordPress to create a new post, not update an existing one
$post[ 'ID' ] = '';
switch_to_blog( $blog_id );
// insert the post
$inserted_post_id = wp_insert_post( $post ); // insert the post
// update post terms
wp_set_object_terms( $inserted_post_id, $post_terms, 'category', false );
// add post meta
foreach ( $data as $key => $values) {
// if you do not want weird redirects
if( '_wp_old_slug' === $key ) {
continue;
}
foreach( $values as $value ) {
add_post_meta( $inserted_post_id, $key, $value );
}
}
restore_current_blog();
// if you want just to copy pages, comment this line
wp_delete_post( $post_id );
}
$redirect = add_query_arg( array(
'misha_posts_moved' => count( $object_ids ),
'misha_blogid' => $blog_id
), $redirect );
}
return $redirect;
}
// show an appropriate notice
add_action( 'admin_notices', 'rudr_bulk_multisite_notices' );
function rudr_bulk_multisite_notices() {
if( ! empty( $_REQUEST[ 'misha_posts_moved' ] ) ) {
// because I want to add blog names to notices
$blog = get_blog_details( $_REQUEST[ 'misha_blogid' ] );
// depending on ho much posts were changed, make the message different
echo '<div class="updated notice is-dismissible"><p>';
printf(
_n(
'%d post has been moved to "%s".',
'%d posts have been moved to "%s".',
$_REQUEST[ 'misha_posts_moved' ]
),
$_REQUEST[ 'misha_posts_moved' ],
$blog->blogname
);
echo '</p></div>';
}
}
- Working with bulk actions, you should also know, that for posts the filter will look like
bulk_actions-edit-post
, for custom post typesbulk_actions-edit-{CPT name}
. Or better read a tutorial about bulk actions on my blog. - You could also notice that we passed the maximum 50 sites to get in
get_sites()
function. The thing is that WordPress Multisite network supports unlimited amount of sites, so if you have 1000+ sites in your Network, you’d better think about another implementation of this step, I can give you two hints – maybe you do not need all the sites, in that case site_in parameter will help you, or if you need all of them, you could try to replace the default bulk action element with Select2 with AJAX search, read more about it here. - I used
get_current_blog_id()
as well because we don’t want to duplicate pages to the same website right? If you do, then another tutorial is for you. - Remove line
69
, if you would like to duplicate posts, not to move them. By the way, if you set the second parameter towp_delete_post( $post_id, true );
, posts will be removed permanently, without moving to the trash.
And again, if this code seems difficult, please check my plugin instead.

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 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
Nice ! thanks !
Btw could you share the social sharing plugin that youre using ? its so minimalistic and clean !
Thank you, but it is not a plugin 🙃 just custom coding
Nice! would appreciate tutorial on that !
It best to get away with one plugin and go for custom coding :)
Hi Misha, excellent work, thank you so much for sharing!
Hi Jakub,
Always welcome! 🙂
Hi Misha again,
could you help me include a post thumbnail, please? I added a snippet
$thumbnail = get_post_thumbnail_id($post_id);
and then
set_post_thumbnail($inserted_post_id, $thumbnail);
but the thumbnail wouldn’t copy.
thanks in advance!
Let me clarify – would you like just to set a thumbnail or to duplicate an attachment too?
I would like to copy a post including its thumbnail (featured image). In that moment, any post is copied including images within post content but featured image stays uncopied.
But does it set as a featured image for new posts?
Sorry Misha, I don’t understand the question, I guess. It should not influence any future posts at all, just copy the featured image related to the selected post. Almost each post has its own featured image. Is this what are you asking?
Hi, well done, thanks for sharing.
Just a question, i only need to show on the “main blog” a preview of the posts written on the “satellite blogs”, something like title, excerpt, thumb and href to the original post; i don’t need to get all the post.
It’s possible?
Thanks
Enrico
Hello,
This method only moves the post to the other site.
How to copy/duplicate a post?
Thank you.
Hello Selvi,
Just remove/comment line 69 in the code
Hello,
When copying a post, all custom fields are also copied. Is it possible to ignore e.g. fields such as “music_post”, “video_post” (this is meta key)?
Hello Arthur,
Just add more conditions:
Hi Misha,
Thanks for the wonderful code! Is it possible to configure it to work for pages as well as posts?
Anthony
Hi Anthony,
Yes, it is possible. I decided to update my code for Pages.
How to include its featured image?
You might check this tutorial for the ready code for copying attachments. Once you do that, you can use a new attachment ID as
_thumbnail_id
key of the post.Or as an option you can use my plugin that covers this by default.
Hi Misha,
Great work.
I have a question:
Is it possible to ignore comments once the post has been duplicated?
Please let me know
Thanks
Hi Tommaso,
The comments should be ignored by default. By I think you’re talking about comment count, in this case just add the line
$post[ 'comment_count' ] = 0;