WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a new set of image sizes for my site's in-development theme. My server is filled with old image sizes, most of which have never been used. You've heard this one before, right?

The Question
How do I delete all WP generated thumbnails from my server but not those thumbnails currently used in posts?

That is to say something like: $ find . -name "*-*x*.jpg" | xargs rm -f is too wide of a hammer because myimage-300x250.jpg might be used in a post. Its 6 other variations are welcome to go away though!

I think I need to generate a list of used images, compare that to my directory and delete the difference (minus the originals). But how to go about that escapes me.

The "why?" sidebar:
Extra image sizes on the server is generally no big deal but we have over 2.7 gigs in their right now and it just seems a waste to leave them and back them up. Plus, we're about to start fresh - why not do the spring cleaning?

As far as regenerating the thumbs, while the new design has new image specifications, none of us have the time to go and update hundreds of older posts with RTE inserted images. Let bygones be bygones, I say.

share|improve this question
How about deleting unattached images from your Media library – Brad Dalton Feb 26 '14 at 4:20
Thanks @BradDalton but that wont touch the generated thumbnails. – Will Feb 26 '14 at 5:18
You could delete the unattached files from the media library and then use Force regenerate thumbnails to delete the thumbnails that are the wrong sizes. – mantis Feb 26 '14 at 11:27
up vote 7 down vote accepted

This should get you started:

  1. Loop over all posts and build an array of linked image files
  2. Loop over all image attachments & cross check if any of them have been linked to

Might need to batch it or boost the timeout & memory limit, depending on the server & number of posts you're working with.

$posts = get_posts(
        'posts_per_page' => -1,
        'post_type' => array( 'post', 'page' ),

$files = array();
foreach ( $posts as $post ) {
    if ( preg_match_all( '/src=(?:\'|")(.+?)(?:\'|")/', $post->post_content, $matches ) ) {
        foreach ( $matches[1] as $url ) {
            // Replace "wp-content/uploads/" with your upload directory if not default setup
            if ( preg_match( '!wp-content/uploads/(.+)$!', $url, $matches_2 ) )
                $files[] = $matches_2[1];

$posts = get_posts(
        'posts_per_page' => -1,
        'post_mime_type' => 'image',
        'post_type' => 'attachment',
        'fields' => 'ids',

update_postmeta_cache( $posts );

foreach ( $posts as $post_id ) {
    if ( is_file( $file = get_attached_file( $post_id ) ) ) {
        if ( $meta = wp_get_attachment_metadata( $post_id ) ) {
            $path = dirname( $file );

            if ( preg_match( '!^[0-9]{4}/[0-9]{2}/!', $meta['file'], $matches ) ) {
                // Split date folders with filename for when searching size variations
                $suffix = $matches[0];
            } else {
                $suffix = '';

            if ( ! in_array( $meta['file'], $files ) ) {
                // Original file not in post content
                // unlink( $file ); 

            foreach ( $meta['sizes'] as $name => $data ) {
                if ( ! in_array( $suffix . $data['file'], $files ) ) {
                    // Image size not in post content
                    // unlink( "$path/{$data['file']}" );
    } else {
        wp_delete_post( $post_id, true ); // Might as well clean up

What you do with this is up to you. If you're going to keep the original but delete size variations, make sure you wipe it's existence from the database entry in $meta['sizes'] and save with wp_update_attachment_metadata( $post_id, $meta ).

You could even go so far as to regenerate the new sizes with wp_get_image_editor() & update the old src's in your post content on-the-fly.

share|improve this answer
Wow! Thank you. Messing with it now. – Will Feb 26 '14 at 19:27
Thanks again. Striking how much I just learned about WordPress (and some php!) playing around with your answer! One edit: I had to remove the last $ from '!wp-content/uploads/(.+)!$'. – Will Feb 26 '14 at 20:25
PS. I think you'd get a warm welcome from folks if you wrapped this into a plugin. – Will Feb 26 '14 at 20:25
@Will One day :) And the $ in the regex should've been before the !(answer updated). – TheDeadMedic Feb 27 '14 at 10:55

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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