After using WPML Media, a plugin that creates duplicates of each image for each language, we now have almost 100,000 duplicate images that we're not going to use and need to get rid of.

We need to not only delete these from the filesystem itself, but also need to make sure that deleting all references in the database as would typically happen if one were to manually delete them through the media gallery.

I'm looking for a WP-CLI solution, if it's possible. This solution was very helpful, but it does all images, not just those that are unattached / unused within the system.

How can I bulk delete media and attachments using WP-CLI?

Given another solution, the OP put in the comments that he achieved his solution ultimately with SQL.

How do I delete thousands of unattached images?

I'm no stranger to the command line or mysql, but am not familiar enough with WP tables to create a query to maintain the integrity of the database. If you are, then please suggest a pure database related solution, or PHP script that would hook into the system and do things the "wordpress" way.

I am not looking for a plugin based solution. I have tried DNUI, DX Delete Attached Media, and a score of others that all ended badly.

UPDATE: Using "parent=0" to determine if an image is attached or not was a very clever solution, and I marked it as the answer.

There is a way to legitimately use an image in a post and the parent still equals 0; that's when you visit the image details in the media library, and you copy the full image source URL to be manually pasted into a post. The accepted answer's solution will delete these as well. Because of that, I would like to encourage other solutions too that would take this into consideration.

That would perhaps require scanning the database to find instances of the image name, perhaps similar to the algorithm wp-cli search-replace would use?

3 Answers 3


You can try this (untested) modification to the answer you linked to by @something

wp post delete $(wp post list --post_type='attachment' --format=ids --post_parent=0)

to delete attachments without parents.

To target a given mime type, e.g. image/jpeg try:

wp post delete $(wp post list --post_type='attachment' \
    --format=ids --post_parent=0 --post_mime_type='image/jpeg')


  • Remember to backup first before testing!

  • Unattached images might still be used in the post content or e.g. in widgets.

  • 1
    This is clever solution. Looking for 'parent=0' as an attachment is a great way to determine if it's attached or not.
    – user658182
    Apr 13, 2017 at 18:11
  • For anyone coming across this who may need the same thing, just remember parent=0 does not mean that it would not be included in a page. A person can copy the full URL and include it manually in a post. This would not pick up that and those legitimately used images would also be delete. Marking as answer, but also updating question to encourage other answers that could also take this into consideration. Great solution nonetheless!
    – user658182
    Apr 13, 2017 at 18:13
  • Glad it helped. Thanks for the explanation. Maybe one could search the whole database as you mentioned, for each image, with a bash script, but that might be rather expensive in resources ;-) @user658182
    – birgire
    Apr 13, 2017 at 18:39
  • 1
    Use wp post delete --force if you do not have media trash active.
    – RRikesh
    Feb 26, 2021 at 9:59
  • 1
    Thanks for the tip @RRikesh
    – birgire
    Apr 15, 2021 at 18:32

The accepted answer will not delete unattached media for attachments where the parent doesn't exist anymore... this could happen when parents are deleted via the db or some other process that doesn't mark the attached media parents as 0.

Here is a cli command that will do that check.

wp post delete $(wp db query  --skip-column-names --batch "select DISTINCT p1.ID  from wp_posts p1
left join wp_posts p2 ON p1.post_parent = p2.ID
where p1.post_type = 'attachment'
AND p2.ID is NULL") --force
  • 1
    Props for showing how to perform a custom SQL query and use it's output as input for the wp post delete statement :) Jan 12, 2021 at 10:29

I had a similar issue but it involved images that were taking space without being in the database (some interrupted uploads/crashes, or wp post delete messing up, or restoring from backups). Kind of at the crossroads between this question and that one.

I ended up using a solution based on a variation of the other two answers:

# List all attachment URLs,
#  ... remove the host part and replace by regex fragment "^\./" (first sed),
#  ... replace extension by empty string to match WP resizes (second sed)
#  ... and write list on terminal and in /tmp/files.txt
#  ... and see them in the less pager in case there's a lot of output
wp post list --post_type='attachment' --field=guid 2>/dev/null | \
  sed 's#^.*//[^/]*/#^\\./#g' | \
  sed 's#[.][^.]*$##' | \
  tee /tmp/files.txt | \

# Find all files (not directories) that correspond to media uploads
#   ... NOT matching the ones we found in the database
#   ... and see them in the less pager in case there's a lot of output
find ./wp-content/uploads/20* -type f | \
  grep -E -f /tmp/files.txt -v | \

(the weird sed commands can probably be replaced with a fancy wp db query instead)

You can modify the grep command by adding -c (to count files that match / don't match) or remove -v (to see files that DO have a match in the database).

  • I modified the second command as below to save the result to another file, find ./wp-content/uploads/20* -type f | \ grep -E -f /tmp/files.txt -v | \ tee /tmp/files_delete.txt | \ less Then ran to remove the files, xargs -a /tmp/files_delete.txt -d '\n' rm Jul 10, 2023 at 18:44

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.