Skip to content
ThemesIonic — home
WordPress Tutorials

WordPress Post Revisions Explained

A revision is an ordinary row in the posts table with a different post type. That explains what it saves, what it silently omits, and why the database bloat panic is usually overstated.

Updated 6 min read intermediate

What a revision actually is: a row in wp_posts with post_type set to revision and post_parent set to the post it belongs to. It is not a diff, not a separate table and not a log. Once you see it as an ordinary post of an unusual type, everything else on this page follows.

Revisions and autosaves stored as rows in wp_posts, each pointing at the published post through post_parent.
A revision is an ordinary row with a different post_type, not a diff and not a separate table.

What gets saved, and what does not

A revision copies the versioned fields of the post: title, content, excerpt, and the author and timestamp of the change. That is close to the whole list.

Data Versioned?
Title, content, excerpt Yes
Post meta and custom fields No, unless a plugin adds support explicitly
Categories, tags and other terms No
Featured image No, it is stored as meta
Menu order, template choice, status No
Block markup inside the content Yes, it is part of the content

That second row causes most of the confusion. Restoring a revision on a page built with a field plugin brings back the prose and leaves today's field values in place, which looks like a partial restore because it is one. Plugins that advertise revision support, including Advanced Custom Fields, do extra work to store and restore their own values alongside the revision.

Autosaves are revisions too, almost

The editor autosaves while you type. That autosave is also stored as a revision row, distinguished by its slug ending in -autosave-v1, and there is only ever one per user per post — each autosave overwrites the last. Ordinary revisions accumulate; autosaves do not.

This is why a post you never published can still have a recoverable draft, and why disabling revisions does not disable autosaving. The two are configured separately.

The bloat question, honestly

Revision counts sound alarming. A post edited fifty times carries fifty extra rows, and a site with two thousand posts can hold tens of thousands of revisions. Whether that matters depends on what reads the table.

Core queries filter by post_type, so a normal page load never touches revisions. The real costs are narrower:

  • Backup and migration size. More rows means larger dumps and slower restores.
  • Badly written plugin queries. Anything running a broad read over wp_posts without filtering post type pays for every revision.
  • Admin list screens on posts with very long revision histories.

Count them before deciding it is a problem:

SELECT COUNT(*) FROM wp_posts WHERE post_type = 'revision';

-- The worst offenders, so you know whether it is broad or a few pages.
SELECT post_parent, COUNT(*) AS revisions
FROM wp_posts
WHERE post_type = 'revision'
GROUP BY post_parent
ORDER BY revisions DESC
LIMIT 20;

If the answer is a few thousand rows on a site with a working backup routine, leave it alone. Editorial history is worth more than the disk space.

Limiting how many are kept

The setting lives in wp-config.php, above the line that requires wp-settings.php, as wp-config.php explained describes:

// Keep the last five revisions per post.
define('WP_POST_REVISIONS', 5);

// Or switch them off entirely. Autosave still runs.
// define('WP_POST_REVISIONS', false);

Five is a sensible default for most sites. It preserves a working undo history without unbounded growth. Setting it to false is a decision to have no editorial history, which is defensible on a single-author site and a poor idea anywhere several people edit the same pages.

The constant applies site-wide. To vary it by post type, filter it instead:

add_filter('wp_revisions_to_keep', function (int $num, WP_Post $post): int {
    // Long-lived articles keep more history than throwaway landing pages.
    return $post->post_type === 'landing_page' ? 2 : 10;
}, 10, 2);

Neither approach deletes anything retroactively. Lowering the limit trims a post's history the next time that post is saved, so old revisions on posts nobody edits stay where they are.

Cleaning old revisions safely

Back up first, per how to back up a WordPress site. Then use something that handles the cascade, because a revision can have its own meta rows and term relationships.

With WP-CLI:

# Check the scale first.
wp post list --post_type=revision --format=count

# Delete them properly, letting WordPress clean up related rows.
wp post delete $(wp post list --post_type=revision --format=ids) --force

Without CLI access, use a maintenance plugin rather than raw SQL — the options are covered in WordPress database management plugins. A bare DELETE FROM wp_posts WHERE post_type = 'revision' removes the revisions and leaves their wp_postmeta and wp_term_relationships rows orphaned, which is how a cleanup ends up needing database repair.

After a large deletion, optimise the affected tables so the freed space is actually reclaimed.

Restoring, and its limits

Open a post, find Revisions in the sidebar, and compare side by side. Restoring writes the chosen version's title, content and excerpt over the current values, and creates a new revision of what you replaced — so restoring is itself undoable.

Two limits to remember. Meta and terms do not come back, as above. And the revision browser shows nothing when a post has only one saved state, which reads as broken but is correct.

Why revisions are missing entirely

  • The post type does not support them. Revisions require revisions in the type's supports array. Pages and posts have it; a custom type registered without it silently has no history, as custom post types explained covers.
  • WP_POST_REVISIONS is false, often set by a host or a performance plugin without telling anyone.
  • A cleanup plugin runs on a schedule and has already removed them — though whether that schedule actually fires on time is its own question, covered in how WP-Cron works and why it misses.
  • The content never changed after publishing. No edit, no revision.

Common mistakes

  • Disabling revisions to fix a speed problem that revisions were not causing. Measure first, and see speeding up the WordPress admin for what usually is.
  • Deleting revisions with raw SQL and leaving orphaned meta behind.
  • Expecting a restore to bring back custom fields. It will not.
  • Assuming a lower limit prunes history immediately. It applies on the next save of each post.
  • Using revisions as a backup. They live in the same database that fails.
  • Copying a page to keep an old version. That creates a competing URL. Use the revision history, or duplicate a page knowingly.

Verify

Edit a published post twice, saving between changes, then open the revision browser and confirm both states appear with the right author and time. Restore the earlier one and check that the content reverted and a new revision was created. Then set your limit, save an old post three times, and confirm the count for that post settles at the number you chose.

Frequently asked

Rarely by themselves. Core queries filter by post type, so revisions sit outside them. The genuine costs are backup size and any poorly written plugin query that reads the posts table without filtering.
Post meta is not versioned. A revision copies the title, content and excerpt, not the meta rows. Field plugins that offer revision support hook in specifically to store and restore their own values.
Only if you also remove the meta rows and term relationships that pointed at them. Deleting from the posts table alone leaves orphaned rows behind, so use WP-CLI or a maintenance plugin that handles the cascade.

Related guides