Control WordPress Post Revisions: WP_POST_REVISIONS, Filters, and Purging Old Revisions

WordPress saves a copy of a post every time it is updated — each revision is stored as a post_type = 'revision' row in wp_posts, linked to the parent post via post_parent. On a site where editors update articles frequently — news sites, documentation wikis, WooCommerce product descriptions — the revision rows accumulate rapidly. A site with 5 000 published posts and no revision limit can have 50 000–200 000 revision rows in the posts table, slowing every query that doesn’t explicitly exclude them, inflating database backups, and causing long restore times. WordPress provides two mechanisms to control this: the WP_POST_REVISIONS constant (set in wp-config.php) limits the number of revisions kept per post going forward, and the wp_revisions_to_keep filter provides per-post or per-post-type control. For existing accumulated revisions, WP-CLI and a targeted $wpdb query can clean them up without touching the live post content.

Problem: Your wp_posts table has grown to hundreds of thousands of rows due to uncapped revisions. You need to limit future revisions and purge the existing backlog without losing any published content.

Solution: Add WP_POST_REVISIONS to wp-config.php for site-wide limiting, use the wp_revisions_to_keep filter for CPT-specific limits, and run a $wpdb purge query (or WP-CLI command) to remove excess historical revisions.

In wp-config.php (add above /* That's all, stop editing! */):

<?php
// Keep a maximum of 5 revisions per post (oldest are pruned automatically).
// Set to 0 to disable revisions entirely. Set to true (default) for unlimited.
define( 'WP_POST_REVISIONS', 5 );

Per-post-type control with the wp_revisions_to_keep filter:

<?php
add_filter( 'wp_revisions_to_keep', 'custom_revisions_limit', 10, 2 );

function custom_revisions_limit( $num, $post ) {
    // No revisions for WooCommerce products (content rarely needs rollback)
    if ( 'product' === $post->post_type ) {
        return 0;
    }

    // Keep 10 revisions for long-form editorial posts
    if ( 'post' === $post->post_type ) {
        return 10;
    }

    return $num; // use default (WP_POST_REVISIONS) for all other types
}

Purge ALL existing revisions with a $wpdb query (run once — safe, revisions are recoverable from backup before running):

<?php
function purge_all_post_revisions() {
    global $wpdb;

    // Delete revision post meta first (foreign key constraint on some setups)
    $wpdb->query(
        "DELETE pm FROM {$wpdb->postmeta} pm
         INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
         WHERE p.post_type = 'revision'"
    );

    // Delete all revision posts
    $deleted = $wpdb->query(
        "DELETE FROM {$wpdb->posts} WHERE post_type = 'revision'"
    );

    return $deleted; // number of rows deleted
}

// WP-CLI equivalent (run from server terminal):
// wp post delete $(wp post list --post_type='revision' --format=ids) --force

NOTE: WP_POST_REVISIONS only affects new revisions created after the constant is defined — it does not automatically purge historical revisions. You must run the purge separately. Also, auto-drafts (interim saves while the editor is open) are stored as revisions with post_status = 'inherit' — these are included in the count and are also cleaned up by the purge query. Take a full database backup before running any bulk delete operation.