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;
	
} );

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:

featured images issues when working with network media libraries in WordPress
Different images from different subsites has the same _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

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

Follow me on X