Removing unused media files from WordPress means identifying attachments in wp_posts (post_type = 'attachment') that have no reference in post content, metadata, widget settings, theme options, or builder data — then deleting both the database row and its physical files on disk. Done correctly, it reclaims gigabytes of storage and reduces backup size; done carelessly, it breaks images that weren't detected by a naive string search.
Why WordPress Media Libraries Get So Large
WordPress creates multiple image sizes for every upload. A single 4 MB photograph can generate five or six resized variants — thumbnail, medium, medium_large, large, plus any custom sizes registered by your active theme and plugins. Delete the post using that image and the attachment record is marked as an orphan in the database, but the physical files remain on disk. Do this for three years across a busy site and you have tens of thousands of files that serve nothing.
According to the HTTP Archive, the median WordPress page weight attributable to images is 1,031 KB as of late 2024 — but that figure only reflects served images. The files sitting unused in wp-content/uploads aren't counted, and on client sites I've audited, the ratio of unused-to-used files has ranged from 30% to over 70% for sites with frequent editorial churn.
Several patterns accelerate accumulation:
- Plugin-generated copies. WooCommerce product image regeneration, image optimization plugins that keep originals, and gallery plugins that cache resized versions all leave files behind when plugins are removed or reconfigured.
- Theme switches. Activating a new theme that registers different custom image sizes triggers a full regeneration, leaving the old size files orphaned.
- Bulk import tools. WP All Import and similar tools often upload images even when a product is later deleted.
- Duplicate uploads. Editors drag-and-drop the same asset multiple times when they can't find it via the media library search.
Understanding What "Unused" Actually Means
Before deleting anything, you need a precise definition of "unused" for your specific site. The naive definition — "no post has this attachment ID in its content" — misses several real-world reference patterns:
| Reference type | Where stored | Detection difficulty |
|---|---|---|
Standard post content (<img> tag) | wp_posts.post_content | Easy — string search |
| Featured image | wp_postmeta (_thumbnail_id) | Easy — meta query |
| ACF image field | wp_postmeta (serialized) | Medium — requires field key mapping |
| Page builder JSON (Elementor, Bricks) | wp_postmeta (_elementor_data, etc.) | Medium — JSON search in serialized data |
| Widget and sidebar data | wp_options (serialized) | Hard — deep in serialized arrays |
| Theme Customizer settings | wp_options (theme_mods_*) | Hard |
| Hard-coded in CSS / child theme | Filesystem | Manual |
| External CDN references | Off-database | Not detectable programmatically |
Page builders store their layout data as JSON blobs inside wp_postmeta. An image referenced inside an Elementor widget shows up in _elementor_data as an attachment ID, but a naive content search won't find it. For more context on how page builders store data differently, see the WordPress Page Builders Compared: Elementor, Bricks, Breakdance deep-dive — the data storage section is directly relevant when planning a media audit.
Step 1: Back Up Before You Touch Anything
This is not a boilerplate warning. Bulk media deletion is one of the few WordPress operations that is genuinely difficult to reverse. Deleting an attachment removes the database row and the physical files; without a backup that predates the deletion, the images are gone.
Your backup must include:
- A full database dump (
wp_posts,wp_postmeta,wp_options, and any custom tables used by your builder or ACF) - A full copy of
wp-content/uploads
The uploads directory on a large site can be 20–50 GB. Verify the backup completed successfully and, critically, test a restore on a staging environment before running any delete operation on production. A backup you've never tested is a backup you can't trust.
See the WordPress Backup Strategy: The Complete Guide to Protecting Your Site for a full breakdown of full vs. incremental approaches, RTO/RPO planning, and how to validate restores — all directly applicable here.
Step 2: Clone to Staging and Run the Audit There First
Never run a bulk media audit directly on production. Clone your site to a staging environment, run the detection tools there, and review the output before touching live files. This lets you catch false positives (images your tool thinks are unused but actually are referenced) without any production risk.
If your host doesn't provide one-click staging, that's a workflow problem worth solving at the infrastructure level. TopSyde's managed hosting for developers and agencies includes staging environments as a standard feature, not an upsell.
Step 3: Plugin-Based Detection
For most sites, a well-configured plugin gives you 80% of the picture quickly. The tools worth evaluating are:
Media Cleaner (Meow Apps) — The most mature option. It scans post content, metadata, and theme options, and provides a "trash" intermediate state before permanent deletion. Paid version adds ACF and builder compatibility. Limitation: serialized data detection isn't exhaustive.
WP-Sweep — Lightweight, focused on database cleanup (revisions, transients, orphaned meta), not media files specifically. Useful as a companion tool, not a primary media scanner.
Image Cleanup — Simpler scanner, good for straightforward sites without heavy custom field usage. Not suitable for complex ACF or builder setups.
Resmushit / Imagify orphan detection — Some optimization plugins now include orphan detection as a sidebar feature. Worth checking if you're already using one.
Plugin configuration tips:
- Set the plugin to "trash" mode, never "delete immediately" on first run
- Review every item flagged before emptying the trash
- Cross-reference flagged items against your list of known critical images (hero images, logos, product photography)
Step 4: SQL-Based Detection for Accuracy
Plugins give you speed; SQL gives you precision. The following query finds attachment IDs that appear nowhere in post content or as featured images:
SELECT p.ID, p.guid
FROM wp_posts p
WHERE p.post_type = 'attachment'
AND p.post_status = 'inherit'
AND p.ID NOT IN (
SELECT meta_value FROM wp_postmeta WHERE meta_key = '_thumbnail_id'
)
AND NOT EXISTS (
SELECT 1 FROM wp_posts parent
WHERE parent.post_content LIKE CONCAT('%', p.guid, '%')
OR parent.post_content LIKE CONCAT('%', p.ID, '%')
)
ORDER BY p.ID DESC;
This is a starting point, not a final answer. Extend it to check your specific metadata patterns:
-- Check for ACF image fields (adjust meta_key patterns to match your field keys)
SELECT meta_value FROM wp_postmeta
WHERE meta_key IN ('hero_image', 'product_gallery', 'team_photo')
AND meta_value REGEXP '^[0-9]+$';
-- Check wp_options for widget and customizer references
SELECT option_value FROM wp_options
WHERE option_name LIKE 'widget_%'
OR option_name LIKE 'theme_mods_%'
AND option_value LIKE '%attachment%';
The options table entries will be serialized PHP, so you'll need to unserialize and inspect them manually or with a tool like phpMyAdmin's search or a WP-CLI script.
WP-CLI approach:
# List all attachment IDs
wp post list --post_type=attachment --fields=ID --format=csv > all_attachments.csv
# Export post content for cross-reference
wp db query "SELECT post_content FROM wp_posts WHERE post_status = 'publish'" > post_content_dump.txt
# Then diff the lists with grep/awk to find attachment IDs not present in content
For sites with significant database bloat beyond just media, WordPress Database Optimization: Fix Slow Queries and Reduce Bloat covers the broader cleanup process including transients, orphaned postmeta, and autoloaded options — all worth addressing in the same maintenance pass.
Step 5: Handle False Positives Systematically
According to Sucuri's 2024 WordPress threat report, the majority of compromised sites had not been audited for orphaned files in over 18 months — and many cleanup operations that broke live sites did so because of undetected ACF or builder references. The false positive problem is real.
Systematic false positive prevention:
-
Map all ACF field keys that store images. Run
wp acf get-field-groups --format=jsonand extract allimage,gallery, andfilefield types. Add their meta keys to your SQL detection exclusion list. -
Search Elementor and Bricks data for attachment IDs. These builders store attachment IDs inside JSON. A
LIKE '%"id":ATTACHMENT_ID%'query againstwp_postmetacatches most cases. -
Check your stylesheet and child theme for hardcoded image paths.
grep -r "uploads" wp-content/themes/your-child-theme/surfaces any hard-coded references that no database scan will find. -
Review navigation menus and custom widgets. Images set as menu item icons or used inside custom HTML widgets live in
wp_optionsunder serialized widget data. -
Check your email templates. Plugins like WooCommerce and various email builders store template images in
wp_optionsor custom tables.
Step 6: Deletion Workflow
Once you have a vetted list of truly unused attachment IDs:
Using WP-CLI (recommended for large lists):
# Dry run first — review what will be deleted
wp post delete $(cat unused_ids.txt | tr '\n' ' ') --post_type=attachment --dry-run
# Actual deletion (removes DB row and associated files)
wp post delete $(cat unused_ids.txt | tr '\n' ' ') --post_type=attachment --force
The --force flag bypasses the trash. Only use it after your dry run confirms the list is correct. Without --force, attachment deletion via WP-CLI sends items to trash, giving you a recovery window.
Batch size matters. On large sites, delete in batches of 200–500 to avoid timeout issues and give yourself logical checkpoints to verify nothing is broken.
Verify after each batch. Spot-check the live site's key pages — homepage, product pages, any page where images are loaded via ACF or builder data — after each batch completes.
Step 7: Clean Up Orphaned Thumbnail Files
WP-CLI's delete command removes the attachment record and the original file, but orphaned thumbnail files (the resized variants) may remain if the attachment record was already deleted previously without cleaning up the filesystem.
# Find image files in uploads that have no matching attachment record
# This requires a custom script — the logic:
# 1. Get all file paths from wp_postmeta where meta_key = '_wp_attached_file'
# 2. List all files in wp-content/uploads/
# 3. Find files in step 2 that have no base match in step 1
Alternatively, the Media Cleaner plugin handles orphaned filesystem files explicitly in its "Files" scan mode, separate from its database scan. Running both passes catches different categories of waste.
What This Is Worth in Practice
Storage reclaimed directly reduces your backup transfer time and storage costs. On a site with 15 GB of uploads where 40% is unused, eliminating 6 GB cuts your backup window meaningfully and reduces the attack surface that TopSyde Sentinel's file integrity monitoring needs to baseline — fewer files means fewer noise signals when scanning for injected malware. For context on how file integrity monitoring relates to site security, see the WordPress Malware Removal: Step-by-Step Recovery Guide.
According to Kinsta's 2024 hosting benchmark data, sites that reduced uploads directory size by 30% or more saw measurable improvement in backup completion times and in some cases improved server I/O performance for image-heavy pages.
The cleanup process itself typically takes 2–4 hours for a competent developer on a site with under 50,000 attachments, and 1–2 days of careful work for very large sites with complex ACF or builder setups. Schedule it during low-traffic hours and communicate the maintenance window to stakeholders.
Frequently Asked Questions
Is it safe to use a plugin to bulk-delete unused media files?
Plugin-based deletion is safe only after you've verified the plugin's detection covers your specific field types (ACF, page builder data, widget settings). Always use the "trash" intermediate state before permanent deletion, and confirm you have a tested backup. Plugins that skip the trash step and delete immediately carry significantly higher risk.
What happens to image sizes when I delete an attachment?
WordPress stores all thumbnail variants as separate files on disk, linked to the same attachment record. When you delete an attachment — either via the admin or WP-CLI with --force — WordPress removes the database row and calls wp_delete_file() on each registered image size variant. If some variants were generated by plugins no longer active (which don't register their sizes anymore), those files may remain on disk and require a separate filesystem sweep to remove.
How do I handle images stored in ACF fields that don't appear in post content?
ACF image fields store the attachment ID in wp_postmeta under the field's meta key. You need to explicitly query those meta keys and exclude any attachment IDs found there from your deletion list. The safest approach is to export your ACF field group configuration, identify every field of type image, gallery, or file, and build those meta keys into your SQL exclusion list before running any delete operation.
Can I recover deleted media files if I made a mistake?
If you deleted via the WordPress trash (not force-deleted), attachments remain recoverable for 30 days from the trash screen. If you used wp post delete --force or deleted directly from the filesystem, recovery requires restoring from a backup — which is why testing your backup restore before the cleanup is mandatory, not optional.
How often should I run a media library cleanup?
For active sites with regular editorial publishing, a quarterly audit is reasonable. For WooCommerce stores with frequent product churn, monthly cleanup of orphaned product images keeps the library manageable. For brochure sites with infrequent content changes, an annual cleanup alongside your broader WordPress backup strategy review is sufficient.
Topics

DevOps & Security Lead
12+ years DevOps, Linux & cloud infrastructure certified
Marcus leads infrastructure and security at TopSyde, managing the server fleet and AI monitoring systems that keep client sites fast and protected. Former sysadmin turned WordPress hosting specialist.



