Taming a Large WordPress Media Library
Somewhere around the ten-thousandth upload, every WordPress media library crosses a line. Backups take hours. The uploads folder is bigger than the database, the theme, and the plugins combined — several times over. Searching for that one hero image from years ago is archaeology. And when you finally check disk usage with your host, the number makes no sense, because you definitely did not upload forty gigabytes of photos.
You didn't. WordPress did, on your behalf, one resized copy at a time. The good news: a large media library is one of the most fixable problems in WordPress, because almost all of the bloat is generated files that can be audited, pruned, and regenerated at will. The bad news: the people who get this wrong tend to get it wrong destructively. So let's do it in the right order — understand, measure, prune, regenerate, and only then delete.
One upload becomes many files
When you upload a single photo, WordPress does not store one file. It stores the original plus a copy at every registered image size. Out of the box, core registers four: thumbnail, medium, medium_large, and large. Then your theme adds its own — hero crops, card crops, gallery squares. Then plugins add more: sliders, related-posts widgets, and commerce plugins are enthusiastic size-registrars (a shop setup can easily add several on its own). A perfectly normal site ends up with eight to fifteen registered sizes, meaning every upload spawns eight to fifteen JPEGs.
Two more multipliers hide on top of that:
- The scaled original. Since WordPress 5.3, uploads wider or taller than a threshold (2560 pixels by default, set by the
big_image_size_thresholdfilter) get a resized working copy with a-scaledsuffix. WordPress uses the scaled copy everywhere and quietly keeps the full-resolution original on disk too. Camera photos are routinely 5000+ pixels wide, so photo-heavy sites carry a shadow archive of huge originals almost nothing ever serves. - Retired sizes never retire. When you switch themes, the new theme registers its sizes and generates them for new uploads — but the old theme's crops stay on disk forever. Three theme changes over a site's life can mean thousands of orphaned crop files no template will ever reference again.
None of this is a bug. Pre-generated sizes are why WordPress can emit responsive srcset markup and serve a 300-pixel card image instead of a 5000-pixel original. The multiplication is the feature. The absence of any garbage collection is the problem.
Measure before you touch anything
Resist the urge to install a cleanup plugin on day one. First, get numbers. With WP-CLI (your host has it; if not, that is a data point about your host):
- How many attachments does WordPress know about?
wp post list --post_type=attachment --format=count - What sizes are registered right now?
wp media image-size— this lists every registered size with its dimensions and crop behavior. Expect to be surprised. - What is actually on disk?
du -sh wp-content/uploadsfor the total, anddu -sh wp-content/uploads/*to see which years are heaviest. - How bad is the multiplication? Compare a file count (
find wp-content/uploads -type f -name '*.jpg' | wc -l) against the attachment count. On a neglected site, ten-to-one is unremarkable.
The gap between 'files WordPress knows about' and 'files on disk' is your treasure map. Everything in that gap is either a generated size or an orphan.
Prune the size list
Every size you stop registering saves one file per future upload, forever. Three levers, in increasing order of assertiveness:
Turn off what core lets you turn off
Under Settings → Media, setting a size's dimensions to 0 stops WordPress generating it. Most sites can zero out at least one of the default sizes without any visible effect — but verify against your theme's templates before assuming.
Deregister sizes nothing uses
For sizes added by a former theme or a deactivated plugin, a small snippet in your child theme or a mu-plugin removes them: remove_image_size( 'old-theme-hero' ) hooked late enough to run after the size was registered (the init hook with a high priority number works). For blunt-force control, the intermediate_image_sizes_advanced filter lets you strip sizes from the generation list at upload time.
Reconsider the threshold
If you never need print-resolution originals, lowering big_image_size_threshold shrinks the working copies. If you genuinely need full-resolution files (photographers do), leave it — just know the originals are there and budget backup space accordingly.
How do you know whether a size is used? Search your theme for calls to the_post_thumbnail, wp_get_attachment_image, and get_the_post_thumbnail_url and note the size names they pass. A size no template names is a candidate — 'candidate' because page builders and sliders sometimes reference sizes from stored settings rather than template code. Which is why the actual deletion step comes last, after a staging rehearsal.
Regenerate without melting the server
Once the registered size list reflects reality, regenerate the files to match. The canonical tool is WP-CLI:
wp media regenerate --yes— rebuild everything. On a large library this is hours of CPU-and-disk work, so run it in a quiet window, ideally insidescreenortmuxso an SSH drop doesn't kill it. Crucially, this also deletes generated sizes that are no longer registered — this is the step that actually reclaims disk from your pruning.wp media regenerate --only-missing— the gentle version: creates sizes that don't exist yet without touching existing files. This is what you want after adding a size (a redesign with new crops), not after pruning.wp media regenerate --image_size=card-thumb— rebuild just one size. Perfect when a single new crop is needed across the library and rebuilding everything would be wasteful.- You can also pass specific attachment IDs to test the waters on a handful of images before committing to the full run.
Do the full-library run on staging first — not because regeneration is dangerous, but because it is the moment you discover that some plugin was silently depending on a size you deregistered. Better to discover that where nobody can see it. And take a fresh backup before the production run: regeneration rewrites thousands of files, and 'restore from last night' is a much better recovery plan than 'regenerate again the other way'.
Shrink the files themselves
Fewer files is half the win; smaller files is the other half.
- Compress on upload. WordPress already recompresses JPEGs (quality 82 by default, adjustable via the
jpeg_qualityfilter). An optimization plugin or an image CDN pushes further with smarter encoders. Rule of thumb: a content image that a human will view in a browser rarely justifies more than a few hundred kilobytes; a hero image approaching a megabyte deserves a second look. - Adopt modern formats. WordPress supports WebP uploads, and recent versions accept AVIF as well. Both typically deliver visibly identical quality at a fraction of JPEG's weight — meaningful percentages, though the exact saving depends on the image. The lowest-friction path is an image CDN or optimization plugin that serves WebP/AVIF to capable browsers automatically while keeping your originals untouched — reversible by design.
- Don't double-optimize. If a CDN converts and compresses on the fly, an additional heavy-compression plugin on origin adds generation cost and quality loss for little gain. Pick one layer to own the job — the CDN layer is usually the better owner, because its output isn't baked onto your disk.
When to offload entirely
Past a certain scale, the answer stops being 'manage the uploads folder better' and becomes 'stop keeping it on the web server'. Offload plugins move media to object storage (S3-compatible buckets) and rewrite URLs to serve from there, usually via a CDN. Consider it seriously when any of these are true:
- Your uploads folder dominates backup time or exceeds your host's storage tier on its own.
- You run multiple servers or containers, where a shared writable uploads directory is a genuine architectural headache.
- Media traffic is a measurable share of server load.
Two honest warnings. First, offloading is a commitment: URL rewriting touches every image reference, so migrating away later is a project, not a checkbox. Second, offloading does not fix a bloated size list — it just relocates the bloat and bills you for it monthly. Prune first, then offload what remains.
Deleting orphans: the step everyone rushes
Now — and only now — the part people want to start with: deleting unused images. The reason it comes last is that 'unused' is genuinely hard to determine. An image can appear in none of your post content and still be load-bearing: referenced from a page builder's serialized meta, a theme option, a slider's settings row, a widget, or a CSS background set in the customizer. Cleanup plugins scan for references, and the good ones scan broadly (content, meta, options) — but no scanner can prove a negative, especially against serialized data and values assembled at runtime.
So the safe procedure looks like this:
- Full backup, files and database, verified restorable.
- Scan with a media-cleanup tool and review its 'unused' list skeptically. Sort by upload date; ancient files from a retired design are safer bets than last month's uploads. Note that 'unattached' in the Media Library's filter only means the image wasn't uploaded while editing a post — it is not evidence of being unused, so don't bulk-delete on that filter alone.
- Quarantine, don't delete. Move candidates to trash (or let the plugin's trash-first mode hold them) and live with the site for a couple of weeks. Broken images have a way of surfacing on the pages you forgot existed.
- Then purge, and enjoy the disk space.
If the library is enormous and mostly historical, consider the pragmatic alternative: archive the whole uploads folder to cold storage, delete aggressively, and accept that you may occasionally restore a file on request. For a decade-old blog carrying gigabytes of images for posts nobody reads, that trade is often better than a perfect audit nobody has time for.
Keep it tamed
A one-time cleanup decays without habits. Three cheap ones:
- Name files before uploading.
team-retreat-group-photo.jpgis findable;IMG_4382.jpgis landfill. The media search box only works if the words are there to find. - Add alt text at upload time. It is an accessibility obligation anyway, and it doubles as searchable metadata.
- Re-audit annually. Run
wp media image-sizeanddu -sh wp-content/uploadsonce a year, after any redesign, and after adding any plugin that touches images. Ten minutes of measurement beats another archaeology dig in three years.
The short version
WordPress multiplies every upload by the number of registered image sizes, keeps full-resolution originals behind a -scaled copy, and never garbage-collects any of it. Measure with WP-CLI, prune the size list to what your current theme actually uses, rebuild with wp media regenerate (on staging first, backup always), shrink what remains with modern formats, offload if scale demands it, and treat orphan deletion as the careful final step rather than the exciting first one. The uploads folder didn't get big overnight, but unlike most WordPress problems, this one shrinks on command.
Photo: George Milton on Pexels