Bulk deletion is one of those jobs that feels harmless until a filter is one click wider than intended. WordPress already provides two strong tools: the dashboard for visible, recoverable batches and WP-CLI for precise automation. The safest workflow uses both ideas—preview first, trash second, verify third, purge last.
Choose the least powerful method that fits
Dashboard: best for tens or hundreds of posts that a human can filter and review. It uses WordPress permissions, hooks, and Trash behavior.
WP-CLI: best for repeatable selection, large inventories, remote operations, and auditable ID lists. Run it as the site owner from the correct WordPress installation.
A maintained deletion plugin: justified only when its tested filters or scheduling add value you cannot achieve with core tools. Review publisher, updates, capabilities, and deletion semantics first.
Direct SQL: not recommended for normal cleanup. Posts have metadata, taxonomy relationships, comments, caches, and plugin data that WordPress APIs know how to handle.
Before deletion: define the boundary
Write the target as a sentence: for example, “draft
postrecords created before a reviewed date by one author.”Confirm whether revisions, attachments, translations, products, orders, and plugin custom post types are explicitly in or out of scope.
Check legal retention, editorial holds, analytics needs, redirects, syndication, search indexing, and downstream integrations.
Pause content-generation/import jobs that could create or mutate records while the selection is being reviewed.
Make sure the backup includes the database and, when attachments are involved, the uploads/object-storage data.
Create a database rollback point
wp db export before-bulk-delete.sql
ls -lh before-bulk-delete.sqlWhat this backup proves—and what it does not
wp db exportuses the active WordPress database configuration and writes an SQL dump without deleting content.The size check catches an obvious zero-byte/missing export, but only a restore test in an isolated environment proves recoverability.
A database dump does not include files under
wp-content/uploads, external object storage, server configuration, or secrets.Store the export outside the public web root with restricted access; it may contain user and authentication-related data.
Dashboard method: reviewable and recoverable
Open Posts → All Posts, then use status, date, category, author, and search filters to narrow the table.
Open Screen Options and choose a manageable number of rows. Extremely large pages can hit PHP memory/time limits and make visual review unreliable.
Select the visible posts, choose Move to Trash under Bulk actions, and select Apply. Repeat page by page rather than assuming the header checkbox selected records beyond the current filtered view.
Open Trash immediately, check the count and several representative titles, then verify public URLs and dependent features.
Use Restore for mistakes. Choose Delete Permanently or Empty Trash only after the recovery window and review are complete.
WP-CLI method: preview before mutation
wp post list \
--post_type=post \
--post_status=draft \
--fields=ID,post_title,post_date,post_status \
--format=tableHow the selector works
--post_type=postprevents pages and custom post types from joining the result accidentally.--post_status=draftrestricts the query to drafts. Adapt only one condition at a time and review the table after every change.--fieldskeeps identity, title, date, and status visible so a human can spot an over-broad result.wp post listreads only at this stage. Redirect the reviewed IDs to an access-controlled change record when auditability matters.
Move a reviewed set to Trash
wp post delete 123 456 789Risk level: caution. Review the command before running it.
Replace every placeholder deliberately
123 456 789are examples, not literal production targets. Copy only IDs from the reviewed preview.Without
--force,wp post deletemoves normal posts to Trash when that post type supports Trash.WP-CLI invokes WordPress deletion APIs and related hooks; plugins may perform additional cleanup or remote actions. Test those effects on staging.
Save the command and output with the change ticket. A successful line for each ID is stronger evidence than assuming the batch completed.
Verify the post state after trashing
wp post list \
--post__in=123,456,789 \
--fields=ID,post_title,post_type,post_status \
--format=tableWhat to verify beyond the command
The selected IDs should now report
trash; unexpected types or statuses require investigation before any purge.Visit representative old URLs while authenticated and logged out. Decide whether they should return 404/410 or redirect to a relevant maintained resource.
Check menus, related-content widgets, internal links, sitemaps, feeds, search indexes, API clients, and caches.
Monitor PHP/server logs and integration queues for hook failures triggered by the bulk operation.
Restore mistakes while Trash still exists
wp post update 123 456 --post_status=publishRisk level: caution. Review the command before running it.
Restoration needs editorial review
wp post updatechanges the selected records back to the requested status through WordPress. Use their original statuses if they were not all published.Restoring the database row may not reverse webhook notifications, cache invalidations, search removals, CDN changes, or third-party sync actions.
Verify permalinks, taxonomy, featured media, translations, and scheduled/publication dates after recovery.
For a widespread mistake, restoring the validated backup may be safer than reconstructing status one post at a time—coordinate the maintenance window first.
Permanently empty Trash in controlled batches
wp post list --post_type=post --post_status=trash --format=ids | \
xargs -r -n 100 wp post delete --force --defer-term-countingRisk level: destructive. Review the command before running it.
Why every flag matters
The left side selects only trashed records of the
posttype. Preview the same query as a table immediately before this command.xargs -r -n 100skips an empty input and submits batches of at most 100 IDs on GNU systems; confirm your host’sxargssemantics.--forcebypasses Trash and permanently deletes each selected record. Recovery then depends on backups and external systems.--defer-term-countingpostpones taxonomy recount work across each command batch for efficiency. Verify category/tag counts and relevant caches afterward.
Multisite, custom types, and scale
On multisite, add the exact
--url=https://site.example/target and printwp option get siteurlbefore mutation. A network install is not a single content pool.Inspect registered post types and their Trash behavior; commerce/order and learning-management records may have business workflows far beyond
wp_posts.For very large sets, page through deterministic ID/date ranges and checkpoint each batch. Avoid a single shell expansion that can exceed argument limits or transaction/runtime boundaries.
Run under maintenance/change-control practices when deletion hooks invalidate expensive caches or call external services.
Afterward, rebuild only the caches/indexes documented by the active stack and verify database health; do not run generic “optimization” commands blindly.
Troubleshooting
Dashboard request times out: lower items per page and process smaller batches; inspect PHP/server logs for the actual limit.
WP-CLI targets the wrong site: stop, print
wp option get homeandsiteurl, restore if necessary, then use explicit--path/--url.Some IDs remain: inspect post type, status, permissions, plugin hooks, and WP-CLI output rather than rerunning a broader selector.
Trash vanished automatically: WordPress normally schedules trash cleanup according to
EMPTY_TRASH_DAYS; hosting/plugins may change it. Treat Trash as temporary, not a backup.URLs now generate search errors: restore valuable content or implement specific relevant redirects; never redirect every deleted URL to the homepage.
Counts look wrong: finish all batches, allow deferred term counting to complete, then run application-specific cache/index maintenance and verify again.
Related WordPress operations
Before a cleanup, back up a WordPress site safely.
Reduce database risk by understanding WordPress database optimization.
For subscriber workflows, see adding an email subscription form to WordPress.
Primary references
WordPress documents the Posts screen, filters, Screen Options, selection, and bulk actions.
The official WP-CLI reference defines `wp post delete`, including Trash,
--force, multiple IDs, and deferred term counting.The WP-CLI `wp post` command reference documents supported content operations.
Comments and corrections