Every house has a junk drawer, the one that collects things nobody wants to decide about yet. Batteries, cables, a key to something. A WordPress media library fills up the same way, except faster, and mostly without anybody putting anything in it.
I counted the uploads folder on the site you are reading this on before writing this. It is a small blog on a theme I wrote myself, with no page builder and no plugin stack to speak of. Here is what is actually on disk.
- Images uploaded
- 187
- Files on disk
- 1,544
Eight files exist for every one a person chose to upload. Of the 490 MB they take up, 236 MB is copies. Nobody was careless here. That is the default behavior of the software, running exactly as designed, on a site that is deliberately kept lean.
So if your library is a mess, start from the assumption that it is not your housekeeping. It is that WordPress has no housekeeping of its own, and never has.
One upload is never one file
Drop a photo into the media library and WordPress immediately makes copies of it at several widths, so a phone is not handed a 3000 pixel file. Core registers thumbnail, medium, medium_large, large and a couple of larger ones on top. Then every theme and every plugin adds its own through add_image_size(), and none of them ask first.
You can see the real number on any site with one command:
wp media image-size
On this site that prints sixteen registered sizes. Six belong to core. Three are mine, for the product cards and the ticker. The other seven are the 24 pixel blur placeholders my theme generates so images fade in instead of popping. All sixteen are made for every image, whether or not that image will ever appear in a place that asks for them.
There is a second copy most people never hear about. Since WordPress 5.3, an upload wider or taller than 2560 pixels gets scaled down, and the scaled version becomes what the site actually serves. The file you uploaded is kept on disk anyway, at full size, forever. Photos straight off a camera or a phone cross that line every time. On this site 47 files are sitting there in that state.
These are not duplicates in the sense anybody means by the word. They are size variations of one image, most of which no page will ever request.
Then you change the theme
This is where the first cause doubles, and then trebles. A theme registers its own set of sizes, and those sizes are baked into every image that was in the library while that theme was active. Switch themes, or take an update where the author changed a card from 600 pixels to 720, and the old files do not go anywhere. WordPress does not track which theme asked for what, so it has no way to know the old set is now dead weight.
A library that has been through three themes carries three generations of variations for the same handful of photographs. This is usually the single largest pile on an older site, and it is invisible in the admin: the Media screen shows you 187 images, not 1,544 files.
WP-CLI knows the difference and will clear it out:
wp media regenerate --delete-unknown
That deletes thumbnails belonging to sizes nothing registers any more, and leaves the current ones alone. Take a backup first. Everything in this post assumes you took a backup first.
Demo imports stack on top of each other
Premium themes ship demo content, and importing a demo pulls down the whole image set that demo was photographed with. Run the import once and that is a fair trade. The trouble starts on the second run.
Most importers do not check whether they have downloaded a file before. So a demo you import twice, or a second demo from the same theme that reuses the same photographs, arrives as a fresh set. WordPress renames the collisions to hero-1.jpg, hero-2.jpg, and now you have two or three complete copies of a demo library, each with its own sixteen size variations behind it. People try a demo, decide they prefer another one, import that too, and never connect the slow admin six months later with the afternoon they spent choosing a layout.
This is the one part of the problem I have actually solved rather than written about. In the framework our themes run on, the importer stamps every file it sideloads with the URL it came from, and checks that stamp before downloading anything. A second import, a different demo sharing the same photographs, or a run that failed halfway and resumed all reuse the attachment that is already there. It is a narrow claim and I want to keep it narrow: it prevents the demo importer from being a duplicate factory. It does nothing about the size variations above, which are core behavior, and nothing about a file you upload twice by hand.
The duplicates that really are duplicates
The human layer is the smallest of the four and the easiest to describe. The same file goes in more than once under different names, because the media library is a bad place to look for anything.
-
Two people upload the same logo, one as
logo.pngand one ascompany-logo-final.png. - You cannot find an image you know you uploaded, so you upload it again. Search only matches the filename and whatever you typed into the title field, which for most files is nothing.
-
A file gets replaced by uploading a new version beside it,
screenshot-updated.png, and the old one is never deleted because deleting things feels risky.
These are worth fixing because they are the only ones a person can prevent by changing a habit. They are also the ones people feel guilty about, which is backwards: they are a rounding error next to the two causes above.
Unattached does not mean unused
Every cleanup tool offers to show you images that are not attached to a post, and this is the single most dangerous button in the category.
Attachment in WordPress records which post an image was uploaded from, once, at upload time. It is not a record of where the image is used. An image can be missing that link and still be on your homepage, and the common cases are not exotic.
| Before you delete anything | A scanner sees it | It does not |
|---|---|---|
| Where it lives | Inside post content | Page builder data |
| Featured images | Attached and linked | Term images |
| Custom fields | Stored as an ID | Stored as a URL |
| Site settings | Logo and site icon | Theme options, CSS backgrounds |
An image referenced by URL in a builder field, a background declared in a stylesheet, a header uploaded through a theme options panel: all of them look unused to a scanner that reads posts and post meta. Delete on that basis and the site breaks in places you will not think to check for weeks.
What it costs to leave it
A fat uploads folder is not just untidy. It shows up in three places that cost money or attention.
- Backups: most backup plugins copy the whole uploads folder every run. Half of my 490 MB is generated files that a regenerate command could rebuild in a minute, and every copy of every backup has been carrying them.
- Hosting: disk is the line item nobody reads on managed hosting, and the plan you get pushed onto when you cross a limit is usually a large jump rather than a small one.
- Moving the site: migrations, staging copies and local clones all slow to the speed of the uploads folder. This is the one that turns a twenty minute job into an afternoon.
It does not directly slow the front end down, and I want to be accurate about that: an unused size variation sitting on disk is never requested, so no visitor pays for it. What does slow the front end is serving an image far larger than the slot it goes in, which is a different mistake with the same root cause, and the admin media screen genuinely does crawl once the library is large enough.
The order to do this in
Sequence matters here more than tool choice, because two of these steps delete things and the rest are cheap. Deleting media in WordPress is permanent and there is no trash to fish it back out of.
- Take a full backup, database and
wp-content/uploadstogether. Restore it somewhere once, so you know it works. - Run
wp media image-sizeand look at what your site actually registers. This tells you how many files each upload is becoming. - Turn off the sizes you do not use. Settings, then Media, and set a size to 0 to stop it being generated. A theme size needs the theme author, or a small snippet.
- Clear out the orphaned generations with
wp media regenerate --delete-unknown. This is the biggest single win on an older site and the safest of the deleting steps. - Now look for real duplicates and unused originals, with a scan rather than by eye. Review what it proposes against the table above before you confirm anything.
- Compress what is left, and put a reminder in the calendar to run steps four and five twice a year.
For step five, three plugins that do the work honestly: Media Deduper indexes files by content and merges genuine duplicates rather than deleting one at random. Media Cleaner scans posts, metadata and a decent list of builders, and moves candidates to its own trash first so you get a second chance. WP-Optimize handles the database side and image compression in the same pass.
If you inherited the site and none of this history is yours
Do steps one and two, then stop. Cleaning a library you did not build means deleting files whose purpose you cannot check, on a site whose custom fields you have not read yet. The uploads folder is not the thing that will bite you first.
Cleaning up is not the fix
A cleanup session gets you back to where you were a year ago. What happens next is unchanged, because every cause is still running. Sizes are generated for each new upload the same way. The next theme change orphans another generation. An importer that does not check will download what it already has, again.
Which is why I would rather fix these where they start. Registering three image sizes instead of ten is a decision a theme author makes once for everybody who ever installs it. An importer that stamps what it downloaded and looks before downloading again is about forty lines of code, written once. Neither is clever work. They are just work that has to happen upstream of the person who ends up with the drawer.
Until more of it happens there, put the twice-yearly reminder in the calendar, and take the backup before you touch anything.
Last updated






