How do I retrieve the IDs of unattached media in my post?

I have two posts: POST A and POST B and a PDF file called file.pdf.

I've uploaded file.pdf to POST A but then also inserted it in POST B.

Now the file.pdf is still attached to POST A so I'm not able to get it's ID by using get_attached_media( '', $post_B_id ).

How can i retrieve the list of all media used in POST B, including files such as my file.pdf - not attached, by inserted into POST B?

  • It's possible but it's not cheap, it's the kind of thing you'd want to do once then cache, doing it on every page load will make things veeeerrryyyy sssllloooooowwwww
    – Tom J Nowell
    Commented Dec 12, 2017 at 16:36

3 Answers 3


Yes you can do this! Is it simple/easy? Sort of... Is it fast and scalable? Oh dear god no 😱.

The Super Expensive Solution

The solution here is this function:

$post_id = url_to_postid( $url );

The problem is that this is a very slow function that's expensive to call. If you call this when displaying every post, your DB server will be under super high strain and may fall over unless you configure it correctly and put it on a dedicated machine.

Note: you can't call this function early, it has to be on the setup_theme hook or later, or it'll cause a fatal error to occur.

With this function, we can pull out every URL in the posts content, and do a test to see if it begins with the URL of our site. If it does, we can check to see if it matches any of the attachments, and if it doesn't, then we know it's unattached and can use this function to grab a copy.

How to retrieve all the URLs in a posts content is a topic or another question however. Testing if a URL is for an attached attachment is a simple if statement comparison in a loop ( foreach attached thing, check if its URL matches the URL we're testing )

Speeding Things Up

There are a few things that might mitigate the cost of this function:

  • Run the process on the save and update posts hook and store the result in post meta
  • Wrap the function in a caching layer to speed things up ( only effective if there's an object caching solution in place such as memcached, not as effective as the previous mechanism ) -Use a less precise method written by Pippins plugins that uses a DB query. This will bypass caches and it's still expensive, but not as expensive as url_to_postid. It also only works with GUIDs, hence the accuracy trade off

An Even More Expensive Solution

Query all the attachments via WP_Query and load all of them into memory, then check each attachment 1 by 1 to see if they appear in your post.

This is by far the slowest and most expensive way to do this:

  • As you upload more media, you have to load more when doing the query, for any site with more than 30 or 40 attachments you're going to get out of memory fatal errors
  • It's a heavy database operation, your DB has to send all of the attachments, and if you've got several people browsing at once, this will get very problematic
  • It will never scale past 5-10 concurrent users, and that's if you're lucky
  • It's slow, checking every attachment takes time
  • 1
    Awesome, thank you! It does look expensive as hell but I need to run this only on very few, particular posts and only on some strict conditions, so it might work for my case. Cheers! Commented Dec 13, 2017 at 14:00
  • @LeviDulstein if you can do it in WP CLI and store the result so it never has to run again, then these kinds of things should be fine. Don't be afraid to create pre-calculated data about the data you already have if it'll give big performance boosts. Precalculation only happens once, and once the price is paid, it stays that way
    – Tom J Nowell
    Commented Dec 13, 2017 at 15:45

You can run a function like so to find and extract your media within a post

add_action('the_content', function($content) {
    $mediaRegex = "/(src|href)=\"(.+?).(jpg|png|pdf)\"/i";
    $mediaFind = preg_match_all($mediaRegex, $content, $media);
    if (isset($media[2]) && count($media[2]) > 0)
        $content = "<pre>MEDIA WITHIN THIS PAGE: \n".print_r($media[2],true)."</pre>".$content;
    return $content;

You can modify the regex pattern to better suit your setup. You can make the regex take classnames of inserted media (instead of urls) and retrieve the attachment ID's that way.

And/or if you want to further refine the media found, ensuring it's in your media library, you can put the media items URLs from your library in a array:

// get the media library for comparison
$library = array();
$args = array(      
    'post_type' => 'attachment',
    //'post_mime_type' => 'image', // if theres only one
    'numberposts' => -1,
    'post_status' => null,
    'post_parent' => null,
$attachments = get_posts($args);
foreach ($attachments as $post)
    $library[] = wp_get_attachment_url($post->ID);

Then write some code to argue with something like in_array() for individual $media[2] matches vs $library. Depending on use, you may want to add Wordpress Transients and wrap the library snippet.


it is not easy to get an short hint, source code to find all usages of an media file. WordPress creates an relationship for an usage to a post type. But you have also the chance to use a media file inside a gallery, plugins, themes, custom code and more.

Core, inside back end

The default of the WordPress core have one option in the back end, inside the Media area. Select all 'unattached' files and you get an list. enter image description here

Alternative plugin

However you should use the core solution above or think about usage of an plugin, that check all this dependencies, like Media Cleaner. I don't have use this plugin, but it checks several usage of an media file and give you an overview about your media files.

  • 1
    Thank's for the reply but that's not what I'm trying to achieve. I don't wan't the list of unattached files. I need a list of media inserted into specific post but (possibly) attached to different post. Commented Dec 12, 2017 at 16:31
  • Then sorry, I miss understand your question.
    – bueltge
    Commented Dec 12, 2017 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.