{"id":5101,"date":"2026-09-02T11:27:37","date_gmt":"2026-09-02T08:27:37","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/?p=5101"},"modified":"2026-09-02T06:17:46","modified_gmt":"2026-09-02T03:17:46","slug":"wordpress-post-revisions-cleanup-safely","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/wordpress-post-revisions-cleanup-safely\/","title":{"rendered":"How to Clean Up WordPress Post Revisions Safely"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><div id=\"toc_container\" role=\"navigation\" aria-label=\"Table of Contents\" data-nosnippet class=\"toc_transparent no_bullets toc_numbered toc_title_center\"><p class=\"toc_title\">\u0130\u00e7indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#The_quiet_cost_of_old_WordPress_revisions\"><span class=\"toc_number toc_depth_1\">1.<\/span> The quiet cost of old WordPress revisions<\/a><\/li><li><a href=\"#Measure_before_deleting_anything\"><span class=\"toc_number toc_depth_1\">2.<\/span> Measure before deleting anything<\/a><ul><li><a href=\"#Count_revisions_with_WP-CLI\"><span class=\"toc_number toc_depth_2\">2.1.<\/span> Count revisions with WP-CLI<\/a><\/li><li><a href=\"#Inspect_sample_rows\"><span class=\"toc_number toc_depth_2\">2.2.<\/span> Inspect sample rows<\/a><\/li><\/ul><\/li><li><a href=\"#Choose_a_cleanup_method_that_matches_the_risk\"><span class=\"toc_number toc_depth_1\">3.<\/span> Choose a cleanup method that matches the risk<\/a><ul><li><a href=\"#Clean_selected_posts_from_the_dashboard\"><span class=\"toc_number toc_depth_2\">3.1.<\/span> Clean selected posts from the dashboard<\/a><\/li><li><a href=\"#Remove_revisions_with_WP-CLI\"><span class=\"toc_number toc_depth_2\">3.2.<\/span> Remove revisions with WP-CLI<\/a><\/li><li><a href=\"#Use_SQL_only_with_a_recovery_plan\"><span class=\"toc_number toc_depth_2\">3.3.<\/span> Use SQL only with a recovery plan<\/a><\/li><\/ul><\/li><li><a href=\"#Keep_a_limit_instead_of_an_unlimited_backlog\"><span class=\"toc_number toc_depth_1\">4.<\/span> Keep a limit instead of an unlimited backlog<\/a><\/li><li><a href=\"#Be_selective_with_cleanup_plugins\"><span class=\"toc_number toc_depth_1\">5.<\/span> Be selective with cleanup plugins<\/a><\/li><li><a href=\"#Why_disk_usage_may_not_change_straight_away\"><span class=\"toc_number toc_depth_1\">6.<\/span> Why disk usage may not change straight away<\/a><\/li><li><a href=\"#Automation_needs_a_stop_condition\"><span class=\"toc_number toc_depth_1\">7.<\/span> Automation needs a stop condition<\/a><\/li><li><a href=\"#Do_not_blame_revisions_for_every_slow_WordPress_site\"><span class=\"toc_number toc_depth_1\">8.<\/span> Do not blame revisions for every slow WordPress site<\/a><\/li><li><a href=\"#Frequently_asked_questions\"><span class=\"toc_number toc_depth_1\">9.<\/span> Frequently asked questions<\/a><ul><li><a href=\"#Is_it_safe_to_delete_all_WordPress_revisions\"><span class=\"toc_number toc_depth_2\">9.1.<\/span> Is it safe to delete all WordPress revisions?<\/a><\/li><li><a href=\"#Will_revision_cleanup_delete_my_posts\"><span class=\"toc_number toc_depth_2\">9.2.<\/span> Will revision cleanup delete my posts?<\/a><\/li><li><a href=\"#How_many_revisions_should_I_keep\"><span class=\"toc_number toc_depth_2\">9.3.<\/span> How many revisions should I keep?<\/a><\/li><li><a href=\"#Why_did_disk_usage_stay_the_same_after_cleanup\"><span class=\"toc_number toc_depth_2\">9.4.<\/span> Why did disk usage stay the same after cleanup?<\/a><\/li><\/ul><\/li><\/ul><\/div>\n<h2><span id=\"The_quiet_cost_of_old_WordPress_revisions\">The quiet cost of old WordPress revisions<\/span><\/h2>\n<p>I once opened a WordPress database on my test VM and found that the content itself was not unusually large. The revision rows were. Nothing was broken, but every backup was carrying years of editing history that nobody had checked for a long time.<\/p>\n<p>WordPress keeps a previous version when you save a post, page, or other revision-enabled content. That lets you recover deleted text, an older title, or a product description with a few clicks. It is a useful safety net.<\/p>\n<p>On a site edited for years, that safety net can become a sizeable part of the database. Multi-author blogs, WooCommerce catalogs, and repeated saves in the block editor can create dozens or hundreds of revisions for one piece of content. Each revision is stored as a separate row in <code>wp_posts<\/code>. Some plugins may also create related data in <code>wp_postmeta<\/code>.<\/p>\n<p>Let me put it this way: revision cleanup is maintenance, not a magic speed button. It can reduce backup sizes and improve some administrative queries, but it will not repair a slow plugin, a bad database query, or an undersized PHP-FPM pool.<\/p>\n<h2><span id=\"Measure_before_deleting_anything\">Measure before deleting anything<\/span><\/h2>\n<p>I do not start database maintenance with a delete command. First I take a backup, confirm where WordPress is installed, and measure the current state. For a handful of posts, the Revisions panel in the dashboard is enough. For a large site, WP-CLI gives me a more useful view.<\/p>\n<h3><span id=\"Count_revisions_with_WP-CLI\">Count revisions with WP-CLI<\/span><\/h3>\n<p>Connect over SSH and change to the WordPress directory. If a command may run for more than a few seconds, I use <code>tmux<\/code> instead of trusting a bare SSH session. A dropped connection should not decide whether maintenance succeeds.<\/p>\n<pre class=\"language-sql line-numbers\"><code class=\"language-sql\">cd \/var\/www\/html\ntmux new -s wp-maintenance\nwp db export before-revision-cleanup.sql\nwp db query &quot;SELECT COUNT(*) AS revision_count FROM wp_posts WHERE post_type = &#039;revision&#039;;&quot;<\/code><\/pre>\n<p>The export creates a database backup, and the final query counts revision rows. Your prefix may not be <code>wp_<\/code>; check <code>$table_prefix<\/code> in <code>wp-config.php<\/code> before using a direct SQL query.<\/p>\n<p>That count is only a starting point. A few hundred rows may not matter on one site, while a large revision backlog can make backups and database work needlessly heavy on a small hosting plan.<\/p>\n<pre class=\"language-sql line-numbers\"><code class=\"language-sql\">wp db query &quot;SELECT post_parent, COUNT(*) AS revision_count\nFROM wp_posts\nWHERE post_type = &#039;revision&#039;\nGROUP BY post_parent\nORDER BY revision_count DESC\nLIMIT 20;&quot;<\/code><\/pre>\n<p>This shows which parent posts, pages, or products have accumulated the most revisions. If one item stands out, I inspect its editing workflow and the plugins that touch it before scheduling a bulk cleanup.<\/p>\n<h3><span id=\"Inspect_sample_rows\">Inspect sample rows<\/span><\/h3>\n<p>I also check actual records rather than trusting a single total. The same query works in the MySQL client or phpMyAdmin:<\/p>\n<pre class=\"language-sql line-numbers\"><code class=\"language-sql\">SELECT ID, post_parent, post_date, post_title\nFROM wp_posts\nWHERE post_type = &#039;revision&#039;\nORDER BY post_date DESC\nLIMIT 20;<\/code><\/pre>\n<p>Most titles will be generic revision titles. A revision normally points to its parent through <code>post_parent<\/code>. Rows with no sensible parent, or revisions belonging to content that was already deleted, deserve a closer look. I do not delete those rows from <code>wp_postmeta<\/code> by guesswork; a plugin may still depend on custom metadata.<\/p>\n<h2><span id=\"Choose_a_cleanup_method_that_matches_the_risk\">Choose a cleanup method that matches the risk<\/span><\/h2>\n<p>The right method depends on the size of the site and how comfortable you are recovering from a mistake. I use the dashboard for a few posts, WP-CLI for controlled bulk work, and direct SQL only when I have a verified recovery path.<\/p>\n<h3><span id=\"Clean_selected_posts_from_the_dashboard\">Clean selected posts from the dashboard<\/span><\/h3>\n<p>For a small number of posts, open the content in the WordPress editor and use its Revisions panel. This is slow across hundreds of posts, but it gives you the clearest control when an editor has overwritten text or a product description needs to be compared with an older version.<\/p>\n<p>Sometimes the safest method is the boring one.<\/p>\n<h3><span id=\"Remove_revisions_with_WP-CLI\">Remove revisions with WP-CLI<\/span><\/h3>\n<p>If the goal is to remove every revision, check the backup before running the deletion. First list the IDs:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">wp post list --post_type=revision --format=ids<\/code><\/pre>\n<p>Reviewing a sample of those IDs is worthwhile, especially on a site with custom post types. For a modest list, you can delete them with:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">wp post list --post_type=revision --format=ids | xargs -r -n 100 wp post delete --force<\/code><\/pre>\n<p>This uses the GNU\/Linux form of <code>xargs<\/code>; <code>-r<\/code> prevents it from running when the list is empty, and <code>-n 100<\/code> keeps the command in manageable batches. WP-CLI deletes each revision as a WordPress post, which also gives WordPress the opportunity to remove metadata associated with that revision.<\/p>\n<p>On systems where <code>xargs -r<\/code> is unavailable, use a shell check instead:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">ids=$(wp post list --post_type=revision --format=ids)\nif [ -n &quot;$ids&quot; ]; then\n  wp post delete $ids --force\nfi<\/code><\/pre>\n<p>For very large sites, I prefer the batched command. Shell command-length limits are an unnecessary failure mode.<\/p>\n<p>My own mistake here was running a maintenance command from the wrong test directory because the shell prompt looked almost identical to the production one. I caught it before deletion, but now I verify the path with <code>pwd<\/code> and the site with <code>wp option get siteurl<\/code> first.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">pwd\nwp option get siteurl\nwp post list --post_type=revision --format=ids | wc -w<\/code><\/pre>\n<p>These checks confirm the directory, the loaded WordPress site, and the number of IDs before you remove anything.<\/p>\n<h3><span id=\"Use_SQL_only_with_a_recovery_plan\">Use SQL only with a recovery plan<\/span><\/h3>\n<p>Direct SQL is fast and unforgiving. I use it only when I have a verified database export, restore access, and a clear reason not to use WP-CLI.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">DELETE FROM wp_posts\nWHERE post_type = &#039;revision&#039;;<\/code><\/pre>\n<p>This removes revision rows from <code>wp_posts<\/code>; it does not remove normal posts, pages, or products. It can also leave metadata rows in <code>wp_postmeta<\/code>, depending on the data and the plugins installed. That is why I do not follow it with a broad, invented orphan-cleanup query.<\/p>\n<p>Before running SQL on a live site, I export the affected rows or test the operation against a staging copy. A transaction can help with some database engines and workflows, but it is not a substitute for a backup, especially when a large delete causes locks or runs longer than expected.<\/p>\n<h2><span id=\"Keep_a_limit_instead_of_an_unlimited_backlog\">Keep a limit instead of an unlimited backlog<\/span><\/h2>\n<p>For most sites, retaining a defined number of recent revisions is a better balance than disabling revisions entirely. Add this to <code>wp-config.php<\/code>, before the line that says <code>That's all, stop editing!<\/code>:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">define( &#039;WP_POST_REVISIONS&#039;, 10 );<\/code><\/pre>\n<p>This limits the number of stored revisions per piece of content from that point forward. It does not remove the revisions already in the database, so an existing backlog needs its own cleanup.<\/p>\n<p>You can disable revisions with:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">define( &#039;WP_POST_REVISIONS&#039;, false );<\/code><\/pre>\n<p>I rarely choose that setting. If an editor changes a price or deletes a section by accident, the revision history may be the quickest recovery point. Five or ten revisions is a reasonable starting point for many sites, but an editorial team with formal approvals may need more.<\/p>\n<p>Be careful while editing <code>wp-config.php<\/code>. I have seen a one-character syntax mistake turn a small configuration change into a 500 response. Make a copy first, edit the file carefully, and check the site immediately afterward.<\/p>\n<h2><span id=\"Be_selective_with_cleanup_plugins\">Be selective with cleanup plugins<\/span><\/h2>\n<p>A dashboard plugin can make revision maintenance easier, but I check more than its list of cleanup options. I look at its update history, compatibility with the WordPress version running on the site, whether it offers a preview, and how easily I can undo its work.<\/p>\n<p>Many cleanup plugins also offer to remove trashed posts, spam comments, transient options, and orphaned metadata. I do not select every checkbox by habit. A value that looks disposable may be configuration or cache data required by another plugin.<\/p>\n<p>Run the first cleanup on staging if you have a usable staging copy. Test the homepage, several posts, the login form, the contact form, and the WooCommerce cart and checkout where applicable. Keep the backup until those checks pass.<\/p>\n<p>To compare the main table sizes before and after the work, I use:<\/p>\n<pre class=\"language-sql line-numbers\"><code class=\"language-sql\">SELECT table_name,\n       ROUND(data_length \/ 1024 \/ 1024, 2) AS data_mb,\n       ROUND(index_length \/ 1024 \/ 1024, 2) AS index_mb\nFROM information_schema.tables\nWHERE table_schema = DATABASE()\n  AND table_name IN (&#039;wp_posts&#039;, &#039;wp_postmeta&#039;);<\/code><\/pre>\n<p>Change the table names to match your prefix. The data and index figures can fall without the operating-system file becoming smaller immediately; InnoDB may retain allocated space for reuse.<\/p>\n<h2><span id=\"Why_disk_usage_may_not_change_straight_away\">Why disk usage may not change straight away<\/span><\/h2>\n<p>This comes up often in support tickets. <code>DELETE<\/code> removes rows, but an InnoDB table does not necessarily return that physical space to the filesystem at once. The deletion may still have worked.<\/p>\n<p>Check the remaining revisions before attempting an optimization:<\/p>\n<pre class=\"language-sql line-numbers\"><code class=\"language-sql\">wp db query &quot;SELECT COUNT(*) AS revision_count FROM wp_posts WHERE post_type = &#039;revision&#039;;&quot;<\/code><\/pre>\n<p>If the count has fallen, the cleanup did what it was supposed to do. <code>OPTIMIZE TABLE<\/code> can rebuild a table and reclaim space in some MySQL and MariaDB configurations, but it may need considerable temporary disk space and can lock or disrupt access to a busy table.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">OPTIMIZE TABLE wp_posts;<\/code><\/pre>\n<p>Run that during a maintenance window, not while a busy WooCommerce store is processing orders. Check the exact database engine and table size first. If the site starts showing connection errors or damaged-table messages, follow the diagnostic order in <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-database-connection-error-fixes\/\">WordPress Error Establishing a Database Connection: Fixes<\/a>. Revision cleanup does not require changing <code>DB_HOST<\/code> or database credentials.<\/p>\n<h2><span id=\"Automation_needs_a_stop_condition\">Automation needs a stop condition<\/span><\/h2>\n<p>A scheduled cleanup is useful only when I can tell whether it ran and whether it did something sensible. I monitor even my home cron jobs with Uptime Kuma on a Raspberry Pi. An unmonitored cron job is a cron job I am merely assuming works.<\/p>\n<p>My preferred order is:<\/p>\n<ol>\n<li>Create a database backup.<\/li>\n<li>Confirm that the newest backup exists and has a plausible size.<\/li>\n<li>Count the revisions before cleanup.<\/li>\n<li>Delete only the records within the planned scope.<\/li>\n<li>Log the result and alert if the count is unexpectedly high.<\/li>\n<\/ol>\n<p>Keep backups away from the same disk as the live database. A disk failure or an incorrect SQL command can make both unavailable together. The guidance in <a href=\"https:\/\/www.dchost.com\/blog\/en\/rpo-rto-and-disaster-recovery-planning-for-small-businesses\/\">RPO, RTO and Disaster Recovery Planning for Small Businesses<\/a> is useful when you are deciding how much backup history and recovery time your site actually needs.<\/p>\n<p>If you write a script around WP-CLI, give it a threshold. For example, stop and notify me if the revision count suddenly jumps from a few thousand to several hundred thousand. That kind of change usually deserves investigation before deletion, not a larger broom.<\/p>\n<h2><span id=\"Do_not_blame_revisions_for_every_slow_WordPress_site\">Do not blame revisions for every slow WordPress site<\/span><\/h2>\n<p>The performance effect depends on the database, the queries, and the way the site is used. A small revision backlog may produce no measurable change. A large backlog can reduce backup time or help particular database and dashboard operations, but the result must be measured.<\/p>\n<p>When a site is slow, revisions are not my first suspect. I check slow queries, PHP-FPM saturation, disk wait, object caching, plugin behavior, and external API calls. On WooCommerce sites, a reporting plugin or an unfiltered query often matters more than old revisions.<\/p>\n<p>As I explain in <a href=\"https:\/\/www.dchost.com\/blog\/en\/hosting-and-seo-myths-vs-reality-about-ips-location-cdns-and-http-2-3\/\">Hosting and SEO: Myths vs Reality About IPs, Location, CDNs and HTTP\/2\/3<\/a>, a cleaner database does not automatically improve rankings. Record your database size, backup duration, homepage response time, and the time required to save a post before and after maintenance. Your own measurements are more useful than a promise attached to a cleanup button.<\/p>\n<h2><span id=\"Frequently_asked_questions\">Frequently asked questions<\/span><\/h2>\n<h3><span id=\"Is_it_safe_to_delete_all_WordPress_revisions\">Is it safe to delete all WordPress revisions?<\/span><\/h3>\n<p>It is generally safe when you have a verified backup and no need for older versions. I still test the process on staging when possible, and I keep a defined number of revisions on sites with active editorial workflows.<\/p>\n<h3><span id=\"Will_revision_cleanup_delete_my_posts\">Will revision cleanup delete my posts?<\/span><\/h3>\n<p>When the command is correctly limited to <code>post_type = 'revision'<\/code>, published posts, pages, and products remain. Check the site URL, table prefix, query scope, and row count before deletion.<\/p>\n<h3><span id=\"How_many_revisions_should_I_keep\">How many revisions should I keep?<\/span><\/h3>\n<p>Five or ten revisions per item is a reasonable starting point for many sites. Teams with several editors or approval stages may need a higher limit. The setting only controls future revisions; it does not clear the existing backlog.<\/p>\n<h3><span id=\"Why_did_disk_usage_stay_the_same_after_cleanup\">Why did disk usage stay the same after cleanup?<\/span><\/h3>\n<p>InnoDB may retain the table&#8217;s allocated space even after rows are deleted. Confirm the remaining revision count first, then consider a table rebuild during a maintenance window with enough free disk space.<\/p>\n<p>My order is simple: back up, verify the restore path, inspect a sample, and only then delete. I still keep revisions enabled. I just do not let an old safety net quietly become the largest object in the database.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Clean up WordPress post revisions without risking your content. Inspect, back up, delete, limit, and automate revision maintenance safely.<\/p>\n","protected":false},"author":4,"featured_media":5098,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[183],"tags":[428,427,429,356,116,402],"class_list":["post-5101","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress","tag-database-cleanup","tag-post-revisions","tag-website-maintenance","tag-woocommerce","tag-wordpress","tag-wp-cli"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5101","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=5101"}],"version-history":[{"count":1,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5101\/revisions"}],"predecessor-version":[{"id":5103,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5101\/revisions\/5103"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/5098"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=5101"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=5101"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=5101"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}