Get Attachment ID from a WordPress Image URL

Taking a WordPress image URL and using it to get the attachment ID is a bit tricky. There are a lot of code samples out there that do this, but they all have problems to some degree or another.

Pippin Williamson posted a very elegant solution based on comparing the URL to the guid in the WordPress database, but that doesn’t really work if the guid gets out of sync or if the URL you have is for a cropped image.

Phillip Newcomer realized that relying on the guid wasn’t the best route and that there should be a way to get the attachment ID even when you have a cropped image URL. So, he wrote some code that strips off the image dimensions that WordPress adds to the end of the file names and then checks the resulting filename against the _wp_attached_file meta key in the database. This works well, most of the time. However, the filename after stripping off the image dimensions doesn’t always match the value of _wp_attached_file, so in those cases it fails.

If we look to WordPress StackExchange, we’ll find code written by Andrey Savchenko (a.k.a. “Rarst”) that predates both of the solutions above and actually overcomes all the issues which were previously mentioned. In fact, my solution is largely inspired by the code he wrote. The only issue with the solution he provided is that there are potentially two queries performed to find a single attachment ID.

My solution was to use the robust and sound logic that Rarst used, but to do the same work using only a single query:

So, the next time you are Googling for a code solution, don’t just use the first code sample you find… even if it works for you. Find some alternate solutions. Read the code and think about use cases where it might break. Most importantly, learn what the code does and don’t just copy and paste. We don’t need to perpetuate bad solutions out of convenience.

I’d like to challenge you to always find one thing you can improve when you decide to use a code snippet. In fact, start with mine… how would you improve it?

Comments

    1. If it is for a different protocol, then you would probably want to do a search and replace to update those instances in your database anyway. If it is a different domain, then you wouldn’t have that attachment in your database. The exception to those use cases as well as the port / auth would be if you are working on a site locally. In this case, you’ll probably want to adapt the function above so you can check against an alternate uploads URL.

  1. Very useful!

    It’s worth noting that this code will fail on PDFs, though. To my knowledge prior to WordPress 4.7 attached PDFs didn’t have any metadata stored at all, so $meta['file'] at line 41 would cause a PHP warning. WordPress 4.7 added support for PDF thumbnail, but still no ‘file’ row in the metadata. For this code to work with PDFs you will need to also look for _wp_attached_file, just like Rarst’s script. This can be done in one single query using tax_query.

  2. Hello,

    this code work for me too.
    BUT: it doesn’t work on multilingual pages (WPML Media plugin). Seems that the $query = new WP_Query( $query_args ); gives a different (wrong) result.

    Do you might have a solution for this?

  3. I changed your excellent function so that if you know the filename without the full url, just pass that in. If the image isn’t in the uploads dir, then the function will return 0 anyway:

    $imageId = getAttachmentId(‘brettlores.jpg’);

    function getAttachmentId($imageName)
    {

    $query_args = array(
    ‘post_type’ => ‘attachment’,
    ‘post_status’ => ‘inherit’,
    ‘fields’ => ‘ids’,
    ‘meta_query’ => array(
    array(
    ‘value’ => $imageName,
    ‘compare’ => ‘LIKE’,
    ‘key’ => ‘_wp_attachment_metadata’,
    ),
    )
    );

    $query = new WP_Query($query_args);

    if ( $query->have_posts() )
    {
    foreach ( $query->posts as $post_id )
    {
    $meta = wp_get_attachment_metadata( $post_id );
    $original_file = basename( $meta[‘file’] );
    $cropped_image_files = wp_list_pluck( $meta[‘sizes’], ‘file’ );
    if ( $original_file === $imageName || in_array( $file, $cropped_image_files ) )
    {
    return $post_id;
    }
    }
    }

    return 0;
    }

  4. Awesome!
    This is what I was looking for after using Pippin Williamson solution. Not working since few days, I finally used your code snippet and it works perfectly!

    Thank you so much 🙂

  5. Worked like a charm thank you so much! Only thing worth adding a point about is you need to use the url path like http.

    And for those who use namespacing you’ll want to do this to be able to make the call. Thanks again!

    $query = new \ WP_Query( $query_args );

  6. BRILLIANT! I’ve been banging my head over this for so long. As you said, other attachment id solutions don’t always work, and I couldn’t understand why not.

    Still don’t understand how the “guid gets out of sync,” as you say, but that must be it, because I was not using cropped images.

    I’ll give our test site url below, which is where we’re implementing this for now. Soon to be on our live site, though.

    THANK YOU!!!

  7. Thanks for sharing this! It has been working great for me. However recently I noticed that the function gets confused if there is more than 1 file with the same name. For example:

    /uploads/2017/08/filename.jpg
    /uploads/2017/06/filename.jpg

    Is anyone else experiencing this?

    1. You make a great point Phil. The reason this is happening is that we use basename() to get the filename and use that in our query. Really, we should just be removing the base upload directory URL from the full URL and using the remainder, which will contain the proper directory path, in our query.

  8. Nice solutions, i have my own solution i query the filename directly to wp_posts, in that case i would always get the id of the image regardless if it was actually attached to any object.

    reason was that since i do it from a sync on a 3rd party system where images will have to be synced before it even had to be attached to any object.

    $id = $wpdb->getvar(“SELECT ID from wp_posts WHERE guid LIKE ‘%image.jpg%'”);

    Though i would like to know if there would be any issue if not using the wp_attachment_metadata. I also employed wp_attachment_metadata on some parts of the code but not when its running on php-cli as the script gets executed via cron.

    1. Well, filenames only have to be unique per each folder in WordPress. So unless all of your images are in one folder instead of organized into folders by year and month (the default), then you could potentially run into issues where there are actually multiple matches. See my previous comment to Phil as to why the current code snippet actually has the same issue.

      Also, unless you do more than just run that SQL query, there is no way to guarantee that the URL wasn’t for an image on another site whose base filename just happened to have the same name. As previously mentioned, you also can’t assume that the URLs in the guid field are correct either.

      One other issue you may run into is if the filename is for a cropped image. What will happen is WordPress won’t find it just by checking the guid field, which is why we are using the wp_get_attachment_metadata() approach.

      The thing I’d be careful of with a custom SQL query is security. Using the $wpdb->getvar() method directly without escaping your SQL query (see https://developer.wordpress.org/reference/classes/wpdb/prepare/) leaves you open to SQL injection attacks. Additionally, you probably don’t want to assume the table name is wp_posts either since it is possible to customize the table prefix in WordPress. You should use {$wpdb->prefix}posts or just $wpdb->posts to properly fetch the name of the posts table.

      Thanks for the question! I think this will help a lot of other people better understand the different use cases and issues at play here.

  9. Hi all,

    Inspecting WordPress code to make a patch to Divi theme, I found an internal WordPress function to get post ID from image URL…
    It’s attachment_url_to_postid(), introduced in WordPress v4.0 😉
    Besides, it looks like it handles protocol !

    Cheers !

  10. I just saw the comment from Bas above that there is a WordPress-core function for this. I will have to try that.

    In the meantime, though, the two references to the PHP function basename() fail for some filenames. For example, I had a filename “©-Michael-Lionstar-300×300.jpg”, note the first character being a copyright symbol.

    Apparently, that’s not even uncommon, as many photographers now require the copyright symbol in the actual filename when people hire them for headshots. (Learn something new every day!)

    Replacing the two instances of basename() with wp_basename() works, though, as it is an “i18n friendly version of basename()”.

  11. > I’d like to challenge you to always find one thing you can improve
    > when you decide to use a code snippet. In fact, start with mine…
    > how would you improve it?

    Ask an ye shall received 🙂

    My approach:

    1. borrows the idea of looking in the sizes field of the _wp_attachment_metadata postmeta from you and @rarst
    2. however, to overcome the problems reported by others above about false positives for identically named files in different sub-dirs of /wp-content/uploads, it also checks that the directory in the URL and the postmeta match

    Additionally, my approach:

    1. is implemented as a func hooked into the attachment_url_to_postid filter, so that attachment_url_to_postid() can be used to find the attachment ID for URLs for both full and intermediate/backup sized images
    2. also looks in the _wp_attachment_backup_sizes postmeta (which is added when you use the WP “Image Editor” to scale/rotate/etc an image)
    3. when searching _wp_attachment_metadata and _wp_attachment_backup_sizes, it searches for the serialized form of the filename (e.g., meta_value LIKE '%s:15:"foo-150x150.jpg";%') to reduce the number of false positives it has to contend with.

    You can find my implementation at https://gist.github.com/pbiron/d72a5d3b63e7077df767735464b2769c.

    There are 2 cases (documented as @todo’s in the gist) that I know of where it produces incorrect results, but they are both the result of bugs in core (the @todo’s link to the trac tickets for those 2 bugs).

    I’d love to have others run some torture tests on pathilogical cases and see if you can find any other cases it misses.

  12. Hi there,

    Tell me if I’m missing something, but you can get attachment ID in a very simple way :

    $attachments = get_posts( [
    ‘post_type’ => ‘attachment’,
    ‘posts_per_page’ => -1,
    ‘post_parent’ => $post->ID,
    ] );

    foreach( $attachments as $attachment ) {
    $image_meta = wp_get_attachment_metadata($attachment->ID);
    echo ‘Image ID : ‘ . $attachment->ID . ”;
    echo ‘Image Object for this ID :’;
    echo ”;var_dump($image_meta);echo ”;
    }

    1. Thanks for the question! You bring up a good point, which is something that I think many people have some misconceptions about.

      Yes, you can get the IDs of attachments “attached” to a post is pretty easy if you have the post ID. Unfortunately, due to how attachments are actually attached to a post, this complicates things a bit.

      Attachments are stored in the posts table in WordPress and when an attachment is actually “attached”, the post_parent field is set in the database to point to the ID of the post to which it was attached. As a result, an attachment can only be attached to a single post. But as we know, images can be used across more than just one post. An attachment is only “attached” to a post if it was directly uploaded from the edit screen of a post, or if you manually “attach” it to a post via the option in the media library. This means we can have attachments that aren’t actually used in a post’s content, and attachments officially attached to another post that ARE used in a post’s content.

      As you can see, the method that you suggest will work… but isn’t very robust because it could leave a number of attachments out or return attachments that aren’t actually used in the content. Fetching all of the image URLs from a post and then using the function I proposed to fetch the image IDs has worked really well for me in resolving these types of issues.

      Another type of use case where you might need to find an attachment ID from a URL is when you have image URLs stored in a database field that can’t be traced back to a post or attachment ID.

      Again, thanks for the question! Hopefully, it will help clear up some things for many people.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.