Skip to content

Repository files navigation

WP-Sweep

Contributors: GamerZ
Donate link: https://lesterchan.net/site/donation/
Tags: sweep, cleanup, optimize, database, revisions
Requires at least: 6.8
Tested up to: 7.0
Stable tag: 2.0.0
Requires PHP: 8.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WP-Sweep allows you to clean up unused, orphaned and duplicated data in your WordPress. It also optimizes your database tables.

Description

WP-Sweep finds the rows WordPress leaves behind and removes them. Revisions of posts you edited years ago, meta belonging to a post that was deleted, terms attached to nothing, term relationships pointing at posts that no longer exist, expired transients: none of it is visible anywhere in wp-admin, and all of it is in your database.

It uses the proper WordPress delete functions wherever they can reach the row, rather than running raw delete queries, so the hooks other plugins rely on still fire. Only the rows the API refuses to touch — orphaned meta whose object ID is 0 — are deleted directly.

Features

  • Revisions
  • Auto drafts
  • Deleted posts
  • Unapproved comments
  • Spammed comments
  • Deleted comments
  • Orphaned post meta
  • Orphaned comment meta
  • Orphaned user meta
  • Orphaned term meta
  • Orphaned term relationships
  • Unused terms
  • Duplicated post meta
  • Duplicated comment meta
  • Duplicated user meta
  • Duplicated term meta
  • Transient options
  • oEmbed caches in post meta
  • Optimizes database tables

Delete functions used

  • wp_delete_post_revision()
  • wp_delete_post()
  • wp_delete_comment()
  • delete_post_meta()
  • delete_comment_meta()
  • delete_user_meta()
  • delete_term_meta()
  • wp_remove_object_terms()
  • wp_delete_term()
  • delete_transient()
  • delete_site_transient()

Known incompatibilities

These plugins keep data in places WP-Sweep reads as orphaned. Protect their meta keys with the filters under Usage before sweeping.

Donations

I spent most of my free time creating, updating, maintaining and supporting these plugins, if you really love my plugins and could spare me a couple of bucks, I will really appreciate it. If not feel free to use it without any obligations.

Usage

Upload the plugin folder to /wp-content/plugins/, activate it through the Plugins menu, and the screen appears at Tools → WP-Sweep. Every sweep is irreversible, so back your database up first.

The screen lists every sweep with a short description of what it removes, how many items that is, and what proportion of the table they are. Tick the ones you want and use the Sweep bulk action, or use each row's Sweep and Details buttons one at a time. Nothing on the screen needs JavaScript: the buttons are ordinary links and the bulk action is an ordinary form.

There is no settings screen. The one thing worth tuning — how many items Details lists, 500 by default — is the wp_sweep_limit_details filter. The cap exists because the whole sample is held in memory and written into the page.

WP-CLI

wp wp-sweep --all
wp wp-sweep revisions
wp wp-sweep revisions auto_drafts deleted_posts

REST API

All three routes need the activate_plugins capability.

GET    /wp-json/wp-sweep/v1/count/<name>
GET    /wp-json/wp-sweep/v1/details/<name>
DELETE /wp-json/wp-sweep/v1/sweep/<name>

Item names

revisions, auto_drafts, deleted_posts, unapproved_comments, spam_comments, deleted_comments, transient_options, orphan_postmeta, orphan_commentmeta, orphan_usermeta, orphan_termmeta, orphan_term_relationships, unused_terms, duplicated_postmeta, duplicated_commentmeta, duplicated_usermeta, duplicated_termmeta, optimize_database, oembed_postmeta.

Filters

  • wp_sweep_postmeta_whitelist (array) — post meta keys that must never be deleted, by the orphaned or the duplicated post meta sweep. * matches any run of characters. Default: empty.
  • wp_sweep_commentmeta_whitelist (array) — the same, for comment meta.
  • wp_sweep_usermeta_whitelist (array) — the same, for user meta.
  • wp_sweep_termmeta_whitelist (array) — the same, for term meta.
  • wp_sweep_excluded_taxonomies (array) — taxonomies left out of the orphaned term relationships sweep. Default: array( 'link_category' ).
  • wp_sweep_excluded_termids (array) — term IDs left out of the unused terms sweep. Default: each taxonomy's default term, plus any term that is the parent of another.
  • wp_sweep_limit_details (int) — how many items a Details list shows. Default: 500.
  • wp_sweep_capability (string $capability, string $context) — the capability required. $context is one of sweep, ajax or rest.
  • wp_sweep_total_count (int $count, string $name) — the total number of rows a sweep's percentage is measured against.
  • wp_sweep_count (int $count, string $name) — how many items a sweep would remove.
  • wp_sweep_details (array $details, string $name) — the sample list shown by Details.
  • wp_sweep_sweep (string $message, string $name) — the message reported after a sweep has run.

Actions

wp_sweep_admin_post_sweep, wp_sweep_admin_comment_sweep, wp_sweep_admin_user_sweep, wp_sweep_admin_term_sweep, wp_sweep_admin_option_sweep and wp_sweep_admin_database_sweep all fire below the sweep table, in that order.

Protect specific post meta keys from being swept:

add_filter( 'wp_sweep_postmeta_whitelist', function ( $meta_keys ) {
	$meta_keys[] = '_my_plugin_setting';
	$meta_keys[] = '_acme_*';
	return $meta_keys;
} );

Exclude an additional taxonomy from the orphaned term relationships sweep:

add_filter( 'wp_sweep_excluded_taxonomies', function ( $taxonomies ) {
	$taxonomies[] = 'product_type';
	return $taxonomies;
} );

Frequently Asked Questions

The Tools -> Sweep screen has a new address

It is still under Tools, but the address changed from tools.php?page=wp-sweep/admin.php to tools.php?page=wp-sweep. Update any bookmark.

The old address was the legacy "plugin file as menu slug" form, which put the plugin's installation directory name into the page URL. That is also why the screen used to break for anyone who installed WP-Sweep under a different directory name — renamed by hand, or unzipped as wp-sweep-2.0.0. Neither happens now.

Where did Sweep All go?

Every row has a checkbox and the table has a Sweep bulk action, so ticking the header checkbox and applying it does what Sweep All did — and lets you leave out the ones you do not want.

My snippet calling WPSweep::get_instance() stopped working

The classes are renamed in 2.0.0:

  • WPSweep is now WP_Sweep
  • WPSweep_Api is now WP_Sweep_API
  • WPSweep_Command is now WP_Sweep_Command

So WPSweep::get_instance()->sweep( 'revisions' ) becomes WP_Sweep::get_instance()->sweep( 'revisions' ). There is no compatibility alias, so the old name raises a fatal error rather than failing quietly.

Every filter and every wp_sweep_admin_*_sweep action keeps the name it had.

My script calls wp sweep and WP-CLI says there is no such command

The command is wp wp-sweep in 2.0.0. sweep was a name any plugin could have claimed, and WP-CLI hands that collision to whichever plugin registered last; naming it after the plugin makes the clash impossible. Everything after the command name is unchanged, so wp sweep --all becomes wp wp-sweep --all.

My REST client gets a 404 from sweep/v1

The namespace is wp-sweep/v1 in 2.0.0, for the same reason as the WP-CLI command. The three routes, their methods and their responses are otherwise identical, so only that part of the URL changes.

Which filters can I use to protect data from being swept?

Four per-type filters take a list of meta keys that must never be deleted, and each supports * as a wildcard:

add_filter( 'wp_sweep_postmeta_whitelist', function ( $keys ) {
	$keys[] = '_my_plugin_setting';
	$keys[] = '_acme_*';
	return $keys;
} );

The same applies to wp_sweep_commentmeta_whitelist, wp_sweep_usermeta_whitelist and wp_sweep_termmeta_whitelist. Terms are protected with wp_sweep_excluded_termids, and taxonomies are kept out of the orphaned term relationships sweep with wp_sweep_excluded_taxonomies.

In 2.0.0 these lists also protect the duplicated meta sweeps, which the documentation always said they did and the code never did.

A key I protected was swept anyway, or far too much was kept

Before 2.0.0 the exclusion was a SQL LIKE clause, and LIKE treats an underscore as a single-character wildcard. A pattern of _my_key therefore also matched Xmy!key, and a good deal else. Matching happens in PHP now, so an underscore is an underscore and only * is a wildcard. Check your list if you were relying on the old behaviour.

Does WP-Sweep leave anything behind when I delete it?

Nothing. No option rows, no database tables, no capabilities and no scheduled events. uninstall.php still runs, deleting wp_sweep_options and wp_sweep_version on every site of a network — not just the first hundred — because early 2.0.0 builds did write them.

Screenshots

  1. Tools -> WP-Sweep, listing every sweep with what it removes
  2. The same screen after a bulk sweep
  3. A Details list, showing a sample of what a sweep would remove

Changelog

2.0.0

  • BREAKING: Requires WordPress 6.8 and PHP 8.2, up from 6.0 and 7.4. A site on an older stack will not be offered the update.
  • BREAKING: The screen stays under Tools but its address changed, from tools.php?page=wp-sweep/admin.php to tools.php?page=wp-sweep. The old form put the installation directory name into the URL.
  • BREAKING: The WP-CLI command is wp wp-sweep, not wp sweep.
  • BREAKING: The REST namespace is wp-sweep/v1, not sweep/v1. The routes are otherwise unchanged.
  • BREAKING: The classes are renamed WPSweep -> WP_Sweep, WPSweep_Api -> WP_Sweep_API and WPSweep_Command -> WP_Sweep_Command. Every filter and action keeps its name.
  • BREAKING: WP_Sweep::$limit_details is gone. Read it with limit_details() and change it with the wp_sweep_limit_details filter.
  • BREAKING: The six wp_sweep_admin_*_sweep actions fire below the single sweep table, in the order they always had, rather than below six separate ones.
  • BREAKING: Sweep All is gone. Tick the header checkbox and apply the Sweep bulk action instead, which does the same thing and lets you leave rows out.
  • NEW: Bulk sweeping. Tick the sweeps you want and run them in one go instead of one click at a time.
  • NEW: Group filters, sortable columns and pagination, from a real WP_List_Table.
  • NEW: The whole screen works with JavaScript turned off. The row actions are ordinary nonced links and the bulk action is an ordinary form post.
  • NEW: The meta key whitelists protect the duplicated meta sweeps as well as the orphaned ones, which the readme always claimed and the code never did.
  • NEW: wp_sweep_capability and wp_sweep_limit_details filters. WP-Sweep has no settings screen: wp_sweep_limit_details is how the Details cap is changed.
  • NEW: Every sweep carries a description of what it removes, shown under its name.
  • NEW: An uninstall.php that cleans up across a whole network rather than the first hundred sites. WP-Sweep stores no option rows of its own.
  • NEW: Restructured into includes/, following the Plugin Handbook.
  • NEW: PHPUnit and vitest suites, and GitHub Actions CI across six WordPress and PHP combinations, single site and multisite.
  • CHANGED: Meta key exclusions are matched in PHP rather than with SQL LIKE, so an underscore in a protected key is an underscore rather than a wildcard.
  • CHANGED: Every database call goes through one method, and every query is prepared.
  • FIXED: A stored XSS on the admin screen. The Details list was built by string concatenation and injected as HTML, so a comment author name containing markup ran as script in the administrator's browser.
  • FIXED: The plugin no longer builds paths from its own directory name, so the admin script loads when it is installed under a directory other than wp-sweep.
  • FIXED: Filtering wp_sweep_excluded_termids to an empty array produced invalid SQL, leaving the Unused Terms count blank.
  • FIXED: A term was excluded from the Unused Terms sweep whenever its ID matched a default_<taxonomy> option, even if that option pointed at a term that no longer exists. WordPress ships default_link_category set to 2, so on most sites whatever term held ID 2 quietly refused to sweep.
  • FIXED: Sweeping without JavaScript deleted data and displayed nothing; the result is now shown.
  • FIXED: The multisite uninstall loop stopped at 100 sites, hydrated whole site objects to read one column, and left the switch stack unwound by one.
  • FIXED: Request parameters are sanitized and validated against the plugin's own list of sweeps.

1.2.0

  • NEW: Per-type meta key filters (wp_sweep_postmeta_whitelist, wp_sweep_commentmeta_whitelist, wp_sweep_usermeta_whitelist, wp_sweep_termmeta_whitelist) to protect metadata from accidental deletion
  • NEW: Documented all available filters in README

1.1.9

  • NEW: Bump WordPress 7.0
  • NEW: Add CLAUDE.md

1.1.8

  • FIXED: Added current_user_can() Check For AJAX Calls

1.1.7

  • FIXED: Pass in default blank string to fix fatal error

1.1.6

  • NEW: Re-org wp-sweep.php to inc/class-wpsweep.php
  • NEW: Bump to WordPress 6.2

1.1.5

  • NEW: Bump to WordPress 5.8

1.1.4

  • FIXED: Replaced %_transient_% with %\_transient\_%. Escape _ in MySQL if not it is being used as a wildcard character. Props @janrenn.

1.1.3

  • FIXED: Changed permissions check to activate_plugins because update_plugins will return false when DISALLOW_FILE_MODS=true.

1.1.2

  • NEW: Changed permission check to update_plugins for better MultiSite compatibility.
  • NEW: Bump min PHP version to 5.6.

1.1.1

  • NEW: wp_sweep_excluded_termids filter.

1.1.0

  • NEW: Added WP Rest API Endpoint support, sweep/v1/count/<Name>, sweep/v1/details/<Name>, and sweep/v1/sweep/<Name>
  • FIXED: Follow as close as possible to WordPress Coding Standards

1.0.12

  • NEW: Bump to WordPress 4.9
  • NEW: Update README to incompatible plugins

1.0.10

  • FIXED: Invalid plugin head 'This plugin has an invalid header.'

1.0.9

  • NEW: Support for Codeclimate
  • FIXED: Uses get_sites() on WordPress 4.6. This should fix deprecated notices.
  • FIXED: Fixes translation placeholder count. Props @pedro-mendonca.
  • FIXED: Use manage_options capability as it conflicts with Admin Menu Editor on multisite installs. Props @EusebiuOprinoiu.

1.0.8

  • NEW: Added wp_sweep_excluded_taxonomies filter to allow more than just link_category taxonomy
  • NEW: Support for WP-CLI wp sweep

1.0.7

  • FIXED: Use custom query to delete Orphaned Term Relationship if wp_remove_object_terms() fails

1.0.6

  • NEW: Delete 'languages' folder from the plugin
  • NEW: Use translate.wordpress.org to translate the plugin
  • FIXED: Works only with WordPress 4.4 because of new term meta

1.0.5

  • FIXED: apply_filters() wrong arguments

1.0.4

  • NEW: The wp_sweep_total_count, wp_sweep_count and wp_sweep_sweep filters, and the six wp_sweep_admin_*_sweep actions

1.0.3

  • NEW: AJAX Sweep All
  • NEW: AJAX Sweeping
  • NEW: View details of sweep
  • NEW: Optimize DB sweep
  • NEW: User hint and confirmation. Props @SiamKreative
  • FIXED: Division by zero. Pros @barisunver

1.0.2

  • FIXED: Use term_id for wp_remove_object_terms()
  • FIXED: number_format_i18n() issues after sweeping

1.0.1

  • NEW: Moved plugin location to WP-Admin -> Tools -> Sweep
  • NEW: Add Deleted Post Sweep
  • FIXED: Use forced_delete for wp_delete_post() and wp_delete_comment();
  • FIXED: If orphaned meta has an object id of 0, use SQL query to delete

1.0.0

  • NEW: Initial release

Upgrade Notice

2.0.0

A major release. There are four things to check before you update from 1.2.0.

Your server has to be new enough. WP-Sweep now needs WordPress 6.8 and PHP 8.2. If your site is on anything older, WordPress will not offer you the update at all — you will simply stay on 1.2.0 until the host is upgraded.

The screen has a new address, but it has not moved. It is still at Tools → WP-Sweep. Only the address changed, from tools.php?page=wp-sweep/admin.php to tools.php?page=wp-sweep, so a bookmark to the old one needs updating. The old form had the plugin's folder name inside it, which is why the screen broke for anyone who installed WP-Sweep under a different folder name.

Sweep All is gone, and there is no settings screen. Every row has a checkbox now, so ticking the one in the header and applying the Sweep bulk action does what Sweep All did — and lets you leave rows out, which it never could. The single setting that briefly existed, how many items Details lists, is the wp_sweep_limit_details filter instead; it still defaults to 500, so if you never changed it there is nothing to do.

Anything scripted against WP-Sweep needs editing. The WP-CLI command is now wp wp-sweep rather than wp sweep, and the REST routes live under /wp-json/wp-sweep/v1/ rather than /wp-json/sweep/v1/. Both were generic names another plugin could have claimed. If a cron job or a deploy script calls either, update it — the old spellings do not fall back, they fail. Code calling WPSweep::get_instance() must become WP_Sweep::get_instance(), and code reading $sweep->limit_details must call $sweep->limit_details(). Every filter and action keeps the name it had.

Two sweeps now remove slightly less than they used to, on purpose. If you protect meta keys with the whitelist filters, those keys are honoured by the duplicated meta sweeps as well as the orphaned ones — which is what the documentation always said. And because the matching moved out of SQL, an underscore in a protected key is now matched literally rather than as a wildcard, so _my_key protects that key and nothing else. Check your list if you were relying on the old behaviour.

If you use the plugin from wp-admin and nothing else, there is nothing to do beyond finding the new menu. Back your database up before you sweep, as always.

About

WP-Sweep allows you to clean up unused, orphaned and duplicated data in your WordPress. It also optimizes your database tables.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages