Copy Media from One Site to Another in a Multisite Network
Since I work with WordPress Multisite a lot, I’ve already had a chance to implement a similar functionality of copying media between sites in some of my plugins. Below, in this guide, I am going to share different ways of how you can do it, too.
The whole tutorial consists of two parts:
- In the first part, I’ll share with you how you can manage media files across your entire WordPress multisite network with the help of plugins.
- In the second part, you will learn how you can copy a media file from one site to another with the help of bulk actions. We are going to create one from scratch. So, it is going to be a programmatic approach. This part is for those who would like to create a solution themselves or to learn how it is actually working under the hood.
What if you’re not using a WordPress Multisite or simply not sure what it is? In each chapter of this tutorial, I will also show you a way of doing a similar thing for standalone sites (not a part of the WordPress Multisite network).
Using a WordPress Plugin to Copy Media Between Sub-sites
This part of the tutorial is going to be a little bit more about media library management, not only about copying a media file to another site of your network.
Let’s take a look at two approaches here.
Copy a media file to another site of a network as a part of a post
In this part, I would like you to consider taking a look at my Simple Multisite Crossposting plugin, which allows you to copy and sync posts within a WordPress Multisite Network and media files of those posts, for example:
- Featured images,
- Media files (images and other types of attachments) in custom fields,
- WooCommerce product images and product gallery images.
- Images from widgets in page builders (Elementor, the Bricks Builder, etc).
Oh, I almost forgot: if you’re not using a multisite network, but just a couple of standalone WordPress sites, it is also possible with my other plugin – Simple WP Crossposting.
Creating a Multisite shared media library
However, if your sites are part of a WordPress Multisite network, why even bother to copy media from one site to another when you can use the original file?
Didn’t expect that? But it is possible with my other plugin – Multisite Shared Media Library.
The whole idea is that at first, you decide which site is going to be the main one, and we are going to use media files from that specific sub-site across the whole multisite network. By default, the plugin takes the first website as a “network media library site”, but you can change it easily in the plugin settings:

After activating the plugin and deciding on the main network library site – congratulations, now the media files from this site are shared across your whole multisite network, and you don’t even need to copy media to another site to use it, which, by the way, is intended to save tons of your server disk space.
Another cool thing about the shared media library plugin is that it also allows you to choose how you would like to display and use your shared media library – whether it will completely replace the media library of a current sub-site or will be displayed in a separate “Global media” tab like in the screenshot below:

Creating a Bulk Action (a Programmatic Approach)
Want to copy media from one site to another programmatically? Let’s do it in this chapter, then!
This is how it is going to work:

A PHP function that allows you to copy a media file from the current sub-site to a target sub-site
Below is the first part of the code, which is basically just a function that allows copying a specific media file with an ID $attachment_id to a site with an ID $blog_id.
Yes, guys, as easy as that!
/**
* Copies attachment to a specific blog within a WordPress Multisite network
*
* @author Misha Rudrastyh
*
* @param int $attachment_id Attachment ID
* @param int $blog_id Blog ID where to move a media file
* @return bool true on success, false if error occurs
*/
function rudr_copy_attachment_to_blog( $attachment_id, $blog_id ) {
// get image path unscaled or you can use get_attached_file() if it is not necessary to copy full-sized originals
$file = wp_get_original_image_path( $attachment_id );
// exit the function if an attachment with this specific ID doesn't exist
if( ! $file ) {
return false;
}
// switching to a blog we are going to copy the image to
switch_to_blog( $blog_id );
$uploads = wp_upload_dir();
$filename = wp_unique_filename( $uploads[ 'path' ], basename( $file ) );
$new_file = $uploads[ 'path' ] . "/$filename";
$new_file_url = $uploads[ 'url' ] . "/$filename";
// copy the media file into another multisite subsite uploads directory
$sideload = @copy( $file, $new_file );
if( false === $sideload ) {
return false;
}
// it is time to insert media file into media gallery
$inserted_attachment_id = wp_insert_attachment(
array(
'guid' => $new_file_url,
'post_mime_type' => mime_content_type( $new_file ),
'post_title' => preg_replace( '/\.[^.]+$/', '', $filename ),
'post_content' => '',
'post_status' => 'inherit',
),
$new_file
);
// make sure this file is included, because wp_generate_attachment_metadata() depends on it
require_once( ABSPATH . 'wp-admin/includes/image.php' );
// update the attachment metadata.
wp_update_attachment_metadata(
$inserted_attachment_id,
wp_generate_attachment_metadata( $inserted_attachment_id, $new_file )
);
restore_current_blog();
return true;
}In the beginning, I had an idea to use only WordPress functions for this purpose, but I gave up very soon, at least because I thought that using only the copy() function was going to be so much faster than the whole wp_handle_sideload() function. Also, I have seen a solution of moving files to a temporary folder with the help of the download_url() function, which really downloads an image using HTTP requests. What?
Also, when copying the same image multiple times, we have this moment:

In the code above, I decided to use this approach because it is the default WordPress behavior. But you can just skip the image if it already exists! To do that, just replace the following lines:
$filename = basename( $file );
$new_file = $uploads[ 'path' ] . "/$filename";
$new_file_url = $uploads[ 'url' ] . "/$filename";
// do not copy file if it is already exists
if( file_exists( $new_file ) ) {
return false;
}And last but not least, if you think your code is too slow, just do this:
// make sure this file is included, because wp_generate_attachment_metadata() depends on it
// require_once( ABSPATH . 'wp-admin/includes/image.php' );
// update the attachment metadata.
// wp_update_attachment_metadata(
// $inserted_attachment_id,
// wp_generate_attachment_metadata( $inserted_attachment_id, $new_file )
//);Yes, I commented (or you can remove) the whole part of the code that creates image sizes on a sub-site. It may be super-slow in terms of performance depending on how many image sizes are registered on a sub-site. So there is a chance you don’t even need it at all.
Creating a custom bulk action
And now let’s create a custom bulk action.
// add bulk action
add_filter( 'bulk_actions-upload', 'rudr_upload_bulk_actions' );
function rudr_upload_bulk_actions( $bulk_array ) {
if( 2 == get_current_blog_id() ) {
return $bulk_array;
}
$bulk_array[ 'rudr_copy_attachment_to' ] = 'Move to Site 2';
return $bulk_array;
}
// perform bulk action
add_filter( 'handle_bulk_actions-upload', 'rudr_multisite_move_media', 10, 3 );
function rudr_multisite_move_media( $redirect, $doaction, $object_ids ) {
// do something for our bulk action
if( 'rudr_copy_attachment_to' === $doaction ) {
$count = 0;
$blog_id = 2;
foreach( $object_ids as $attachment_id ) { // for each media selected
if( rudr_copy_attachment_to_blog( $attachment_id, $blog_id ) ) {
$count++;
}
}
$redirect = add_query_arg( 'rudr_bulk_media', $count, $redirect );
}
return $redirect;
}
// print notices in admin
add_action( 'admin_notices', 'rudr_bulk_action_notices' );
function rudr_bulk_action_notices() {
// but you can create an awesome message
if( ! empty( $_REQUEST[ 'rudr_bulk_media' ] ) ) {
// depending on how many posts have been changed, our message may be different
printf(
'<div id="message" class="updated notice is-dismissible"><p>' . _n( '%d image copied to Site 2.', '%d images copied to Site 2.', absint( $_REQUEST[ 'rudr_bulk_media' ] ) ) . '</p></div>',
$_REQUEST[ 'rudr_bulk_media' ]
);
}
}- I created the only bulk action for just one sub-site; that’s why
$blog_id = 2is hardcoded. But you can use theget_sites()function to do it for all blogs within a network. - Sometimes, an image may not be copied to a sub-site because of an error or because it already exists there (we previously discussed it), so I decided that using a custom
$countvariable is a good idea here. - Since I named the bulk action “Move to…”, I think I need to explain how to move images between sub-sites (not only copy them). It is as simple as one extra line of code, which you will need to insert in the loop:
wp_delete_attachment( $attachment_id, true ).
By the way, if you’re looking for a bulk action programmatic approach for standalone sites, outside of a WordPress Multisite network, you can find an example here.
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
Found this very helpful. Thank you so much!
I’m glad, you’re welcome!
Hi, Misha;
I tried to adopt this into a bespoke plugin for a multisite of mine; but, sadly, could not get it to work, or, rather, adopt to my given surroundings. I’m not enough of a developer to resolve the hard-coded portions limiting the use of the above study into a dynamically adaptable instance by myself.
Is there perhaps a chance you might make this into one complete instance of code which loads the available blog names and allows to copy selectively, not just statically to “store” and from blog with id=2?
That would be most kind.
Thank you, in the event that you will, and best regards –
Hi,
I already have a plugin for that ;)
Hi Misha, just reading these comments. Does your multisite crossposting plugin support copying of media files and associated taxonomies between subsites?
If not do you intent to offer a plugin specifically for media files?
thanks
Luna
Hi Luna,
Here is information about media files. The long story short, the plugin copies media files, which come along with the posts.
Thanks Misha. I think I may have asked the wrong question. I dont want to cross post articles and images, I JUST want to use images from ones subsite across all other subsites as you have shown in you blog article.
So the question is, have you created a plugin that does what you wrote about in the blog article on sharing media across subsites.
thanks
Luna
Yes, I have a shared media library plugin now. I included the info about it into the post as well.
Hello, thank you for your tutorial. It was really helpful, but I have a problem with creating copy of post image with method
wp_insert_attachment(). I have 3 multisite domain and when I use this part of code it always create 5 copies of the same records in posts table with the same post_title, post_name (with _1, _2 etc in the end of filename), Post_type:attachmentand post_mime_type:image/jpeg.Do you have any idea, why this happens and how to solve it?
Thanks! Works great, I only had to change
wp_get_original_image_path()toget_attached_file()because the image is auto-scaled and renamed to -scaled