I get to do a lot of fun projects with my awesome clients on Codeable. This post was inspired by a client that wanted to delete a lot of WordPress posts with an empty title and content. I have no idea how they got there and neither did the site owner – it could have been a failed import of some kind or plugin that went nuts. Nevertheless they needed to be cleaned and this is a perfect job for my favorite WordPress tool: WP-CLI :). I have split the post into an audit and deletion section.
Auditing Empty WordPress Post Titles and Content Combinations
We are going to approach this methodically by doing some MySQL queries with WP-CLI’s wp db query command looking for empty post titles, content and even custom post types.
Empty Post Title
First we are going to find all of the posts with an empty title.
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_title=''" --allow-root
So many were output I decided to count them by piping the output to wc
and counting the lines -l
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_title=''" --allow-root | wc -l
Almost 5000! Next we will check for just posts with empty content
4902
Empty Post Content
Now we can look for posts with empty content
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts where post_content=''" --allow-root
We can count the posts with empty content
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content=''" --allow-root | wc -l
Almost 5000 again! Let’s finally check to see if these are the same posts that also have an empty title
4902
Empty Post Title and Content
Finally let’s check the posts with both empty titles and content
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content='' AND post_title=''" --allow-root
Let us count those too
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content='' AND post_title=''" --allow-root | wc -l
Almost 5000, looks like a pattern 😉
4902
That confirms it! Now we can delete these with WP-CLI
Empty Post Title and Content for Custom Post Types
We can do the same thing for Custom Post Types by adding a post_type
parameter, here we are looking for WooCommerce products so the post_type
column has been set to product
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_type='product' AND post_content='' AND post_title=''" --allow-root
Let’s count these with wc
and specifically the number of lines -l
wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_type='product' AND post_content='' AND post_title=''" --allow-root | wc -l
Now it is time to delete the empty posts!
Deleting Empty WordPress Post Titles and Content Combinations
Back up the database first before you actually delete anything!
wp db export before-empty-post-clean.sql --all-tablespaces --allow-root
Empty Post Title
This WP-CLI command deletes the posts with empty titles and their associated postmeta, the --force
flag deletes them permanently by skipping the trash. The tr
command is used to replace any new lines with spaces to keep the formatting that WP-CLI requires.
wp post delete $(wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_title=''" --allow-root | tr '\r\n' ' ') --force --allow-root
Empty Post Content
Here is a similar command but only targets the deletion of posts with empty post content
wp post delete $(wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content=''" --allow-root | tr '\r\n' ' ') --force --allow-root
Empty Post Title and Content
Finally this command will delete the posts that have an empty title and content
wp post delete $(wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content='' AND post_title=''" --allow-root | tr '\r\n' ' ') --force --allow-root
This will not delete associated postmeta but is faster since it is interacting with the database directly, could leave orphaned postmeta data leftover!
wp db query "DELETE FROM $(wp db prefix --allow-root)posts WHERE ID IN '($(wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_content='' AND post_title=''" --allow-root | tr '\r\n' ','))'"
Empty Custom Post Types
Last example is for deleting those empty WooCommerce products
wp post delete $(wp db query "SELECT ID FROM $(wp db prefix --allow-root)posts WHERE post_type='product' AND post_content=''" --allow-root | tr '\r\n' ' ') --force --allow-root
Sources
WP-CLI post delete command
Replacing newline with space using tr
Delete from MySQL using subquery as condition
after running the CLI commands and cleaning up THOUSANDS of posts w/ empty content/titles, i realized that the command doesn’t filter out by post type.
i will say that *you did* touch upon this in the WooCommerce section. as this site did not have a WC instance, i skimmed over that section and moved on.
BUT, between images, nav menu items, etc etc… doesn’t WP natively leave out some form of combination of title/content on several different post types?
so, whereas i thought i was only cleaning out a few thousand posts, i ended up cleaning out (and i mean CLEANING OUT) a massive portion of the website’s content.
i’m not trying to complain or anything, because this truly was my oversight. BUT, i would like to suggest that you modify your post to cover a bit more ground… so it can more accurately be applied to most WP instances.
stumbling upon your website (via Kinsta) has been a true blessing! WP CLI is so very powerful… but with great power, comes great responsibility. i abused the power 😝
###
more than likely, there aren’t all too many folks who would run into such a problem (as referred to within this post), or need a resolution for it. BUT, i was one of those guys!
i have a MASSIVE blog that i’ve been growing for the last several years. it uses auto-generated content, pulling in from rss feeds & google images. needless to say, there have been several times when something was changed up on one of the several new post generation steps.
when i ran the scripts to pull the counts, i was flabbergasted! WHOAH! TENS OF THOUSANDS of posts with empty content and several hundred posts with an empty title.
well, one can only hope that all of those who attempted to clean them up (using the WP-CLI commands above) heeded your warning, AND MADE A COMPLETE BACKUP!
i didn’t really give it much thought initially. and, it wasn’t until after I had reviewed the results that i noticed, but *then* it clicked.