How to Create a Shared Media Library in Multisite Network
Every once in a while I am receiving questions about my Simple Multisite Crossposting plugin whether it works with shared media library plugins or not (yes – it works). And when I decided to test it I figured it out that there are multiple shared media library plugins out there and every one of them works a little bit differently.
The key idea of most of these plugins is to store all the media on one “main” blog and not necessarily it should be the blog with ID = 1. Then some of them use switch_to_blog()
function every time you’re working with attachments on subsites, the other ones even create attachments in database linked to original files on all subsites (I assume it could lead to issues by the way).
Most of all I like a switch_to_blog()
approach because it seems unharmful, you can even stop using network media library any time, just deactivate the plugin and continue to use your websites as usual.
Now let’s dive into the coding part.
1. Displaying Media Files from a Specific Site Only
First of all let’s make sure that when you either go to Media > Library or add an image into your website content, only the media from the “main” site are displayed there.
It actually can be implemented with a very simple action hook.
add_action( 'wp_ajax_query-attachments', function() {
$network_library_site_id = 2;
switch_to_blog( $network_library_site_id );
}, 0 );
We don’t even care much about restore_current_blog()
function because it is an AJAX request and anyway it exits code execution after displaying media files.
Also we don’t care if we are already on Site 2 if( 2 === get_current_blog_id() ) return;
because it is ok to use switch_to_blog()
on same subsite twice if we are not planning to use restore_current_blog()
after it.
Guys, even after adding this super-simple piece of code you can start using shared media library and insert existing media in posts using the files from the main website. Though I think there could be issues with responsive images feature in WordPress.
2. Uploading Attachments from Subsites
Not a lot of code here either.
add_action( 'load-async-upload.php', function() {
$network_library_site_id = 2;
switch_to_blog( $network_library_site_id );
}, 0 );
This piece of code allows you to upload media from Media > Add New and from posts and move the files to main site (which is the site with ID = 2, we just decided). So all the files are going to be placed in /uploads/sites/2/
.
I also noticed that a little bit more work has to be done if you are going to use “browser upload” or planning to upload media to drafts.
Our network media library is almost ready!
As you can see it is not a big deal since we always insert the original file URLs to posts in WordPress.
3. Deleting Attachments
If you try to delete an attachment when you are on a subsite, you will get “Error in deleting the attachment”. I think the easiest way is just to remove “Delete permanently” link when you’re not on the main subsite.
The only thing, let’s check the current blog using $GLOBALS[ 'current_blog' ]->blog_id
but not get_current_blog_id()
function because we are switched to the main blog anyway.
add_filter( 'wp_prepare_attachment_for_js', function( $response ) {
$network_library_site_id = 2;
if ( $network_library_site_id == $GLOBALS[ 'current_blog' ]->blog_id ) {
return $response;
}
unset( $response[ 'nonces' ][ 'delete' ] );
return $response;
} );
4. Handling Featured Images
But there is one more thing that makes our whole code much more complicated – featured images! WordPress stores the featured image ID in database in wp_postmeta
table under _thumbnail_id
key in case you didn’t know that. And of course if a specific image ID exists on one (main) site it is most likely doesn’t on the other subsites. Or if you had a couple of images uploaded before started using network media library, you might run into this:

_thumbnail_id
, so when we try to insert image on one subsite, the image with the same ID from another subsite is inserted.I hope you’re using Gutenberg block editor, in that case the code to make featured images work globally will be:
add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
return $result;
}
$network_library_site_id = 2;
if( $network_library_site_id === get_current_blog_id() ) {
return $result;
}
if( 0 === strpos( $request->get_route(), '/wp/v2/media' ) ) {
$request->set_param( 'post', null );
switch_to_blog( $network_library_site_id );
}
return $result;
}, 0, 3 );
// add_action('rest_insert_post', 'rudr_insert_featured_image', 10, 3);
// add_action('rest_insert_{post_type}', 'rudr_insert_featured_image', 10, 3);
add_action( 'rest_insert_page', 'rudr_insert_featured_image', 10, 3 );
function rudr_insert_featured_image( $post, $request, $update ) {
$featured_media = $request[ 'featured_media' ];
if( $featured_media ) {
// this is the original from rest controller that does not work
// $result = set_post_thumbnail( $post->ID, $featured_media );
// so we set the _thumbnail_id manually
$result = update_post_meta( $post->ID, '_thumbnail_id', $featured_media );
//now we unset the featured_image (so the REST controller can't interfere)
unset( $request[ 'featured_media' ] );
}
}
In case you’re still on Classic Editor or maybe it is a specific post type you’re using with it, you have to make the magic happen with admin_post_thumbnail_html
hook I guess.
And the last but not least, let’s display a proper image on website front end.
add_filter( 'wp_get_attachment_image_src', 'rudr_get_network_image', 10, 4 );
function rudr_get_network_image( $image, $attachment_id, $size, $icon ) {
$network_library_site_id = 2;
if( $network_library_site_id === get_current_blog_id() ) {
return $image;
}
remove_filter( 'wp_get_attachment_image_src', 'rudr_get_network_image', 10, 4 );
switch_to_blog( $network_library_site_id );
$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
add_filter( 'wp_get_attachment_image_src', 'rudr_get_network_image', 10, 4 );
restore_current_blog();
return $image;
}
I tried to use post_thumbnail_id
filter hook first, but it broke displaying featured images in WordPress admin, so I decided to stick to wp_get_attachment_image_src
.

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
Hi!, when using this in subsite, image title attribute is empty when adds in Elementor, Can you help me?
Hey Christian, easy, do not use Elementor 😼
XD wow, that’s a bit rough. What if we have to use Elementor Pro? Are you working on its compatibility? (I just bought your plugin last week). thanks
Just the truth 🙂 Yes, unfortunately all my plugins are supporting Elementor because people become very upset 🙂
Hi Misha,
Thanks for writing a great post! I tried it and it worked for me for general WordPress pages. However, I was not able to do so for WooCommerce products. When I saved a product, the images from the shared folder will disappear. Have you noticed the same? Thanks!
Hi Misha
Thanks for the great tutorial. So far it works for me with WP 6.3.2 – but I can’t delete an image from the subsite (id: 2) if I uploaded it via the subsite. At
add_filter()
I set network_library_site_id = 1. Do you have any idea?Update: The delete link is missing in the image dialog. In addition, the image in the media library is only visible in gallery mode, it does not appear in the list view.