Writing

What a WordPress media cleaner should find for free — custom fields, WooCommerce and the rest

· Kristoff Bertram

Media cleanup has two halves. One reads post content for file URLs; the other reads custom fields, product galleries and page-builder markup, where a reference is an ID and nothing in it looks like a filename. That second half is the one that ends up behind the paywall — and it is the half that decides whether the delete button is safe.

Here is the shape of the accident. You install a media cleaner on a site with a few thousand files, run the scan, and get a list of images nothing refers to. You tick them, delete, and the library drops by a gigabyte. Two weeks later somebody notices that the product galleries on the shop are half empty, the team page has a row of broken frames, and the social preview image on every post is gone.

Nothing malfunctioned. The scan read post content, found no mention of those files, and reported exactly what it had found: no reference... in the places it looked. The product galleries store attachment IDs in postmeta. The team photos sit serialized in an ACF repeater. The social image is a meta value an SEO plugin wrote. None of those is post content, and the free scan never opened them.

I went and read the public offering of the plugins people actually install for this job before writing any of the following down. The pattern is consistent enough to be worth a page.

Where the paywall usually falls

The two most-installed plugins in this category are free to install and sell a paid tier; several of the smaller ones do not charge at all. In both paid cases the line between the tiers is drawn in the same place, and not where you'd expect. It is not “scan free, delete paid”, and it is not a cap on how many files you can clean. Free tiers do their job, start to finish, on the part of your site that lives in post content. What sits on the other side of the paywall is coverage. Here's what a paid side holds:

Read that list again as a description of a site rather than a feature matrix. A shop built with a page builder, with product fields and an SEO plugin, is a site where the free scan can see almost none of the places images are used — and will confidently list those images as unused. The paid tier is not selling you a better answer. It is selling you the part of the question that was missing.

I want to be fair about why the split falls there, because it is not greed. Reading post content for a file URL is a LIKE query. Reading an ACF repeater means pulling postmeta, matching the field's own key meta to know which rows are image fields at all, unserializing values that may hold arrays inside arrays, and matching a bare integer safely enough that attachment 123 is never found inside wp-image-1234. It is genuinely the expensive half to think of, plan for, and build, and it has to be maintained against every framework it reads. Putting it in the paid tier is a defensible commercial decision.

But. It is just a bad one for the person deleting files, because the half you are missing is invisible. A scan that cannot read custom fields does not report that it cannot read custom fields. It reports a file as unused. Safe to delete.

What to expect free, whatever you install

Treat this as a checklist to run against any cleaner, this one included. Every item below is a place a WordPress site routinely stores a reference without a filename ever reaching post content.

  1. Custom fields, including serialized ones. Image, gallery and file fields, sub-fields inside repeaters and flexible content, and field values stored in block markup where no URL is written at all.
  2. Featured images and product galleries. Both are postmeta; both are how a shop holds most of its pictures.
  3. Any post type. The scan should not care whether a reference lives in a post, a page or a custom post type nobody had heard of.
  4. Options and theme mods. The site icon, the custom logo, widget content, customizer settings — serialized or JSON, and none of it a post.
  5. Term and user meta, and term descriptions. An image in a category description, or a field on a profile, is a reference like any other.
  6. Comments. A file linked in a reply, or a photo on a review.
  7. Resized variants and rewritten filenames. photo-300x200.jpg and photo-scaled.jpg are the same file as photo.jpg, and a percent-encoded filename in a URL is still that filename.
  8. Work in progress. An unsaved edit sitting in an autosave is a use. So is a reference from a trashed post, which can be restored tomorrow.

And one thing that is not a detection question at all: the scan should show you the evidence. Not a count, not a verdict. The evidence — the object the reference was found in, the field or option it was found under, and a link for you to go and look. A cleaner that tells you a file is used, and cannot say where, is asking for the same trust as one that tells you a file is unused and cannot say why.

What Unused Media does with the second half

Freshet Unused Media is on wordpress.org, free, GPL, no account and no key, and the list above is its free tier rather than its roadmap. The custom-fields half of this job is the half the paid tiers are made of; here it is an integral part of a single job done properly.

It reads ACF image, gallery and file fields including serialized values and repeater and flexible-content sub-fields, verified against ACF's own field-key meta rather than guessed at from the meta key's name. It reads ACF field data stored inside block markup, where no URL is ever written. It reads featured images and WooCommerce product galleries, Elementor page data, block editor content and wp-image-N classes, classic [gallery] and [playlist] shortcodes, page-builder shortcodes that carry only an ID, and raw file URLs including resized variants, -scaled files and percent-encoded names. It reads options and theme mods — site icon, custom logo, widgets, customizer settings, serialized or JSON. It reads term descriptions, term meta and user meta, comments and comment meta, excerpts, and unsaved edits held in autosaves. It does not ask what post type any of that lives in.

Then it shows you the evidence, per reference, with a link: which post, which field, which option. The Uploaded to relation is shown too, and explicitly does not count as usage — that unreliable signal is the thing the plugin exists to replace.

The rest of it is about being wrong in one direction. Ambiguity counts as used. I use this line often because it's true. A bare attachment ID in a meta key the plugin has never seen keeps the file. Matches are boundary-checked, so attachment 123 is never matched by wp-image-1234. A reference from a trashed post counts, because a trashed post can be restored. Files uploaded in the last 24 hours count, because an editor may still be placing them. And every file is re-checked in the instant before it is deleted — anything that became used since the scan is skipped, and the result tells you how many were.

You can watch it do all of this without installing anything: open it in WordPress Playground, which boots a throwaway WordPress in a browser tab with the plugin active and a small site already seeded.

What it does not do — at any price

The checklist above is a detection checklist, and detection is the half I have an argument about. There is another half to this category, and it's one to be honest about.

And yes, there is a paid tier, four additions — a Used view that answers the opposite question file by file (agencies love this), WP-CLI, an exportable evidence report and space totals.

Test the thing before you trust it

None of the above matters as much as five minutes on a staging copy. The test is the same for any cleaner and it takes one image.

  1. Put the same image in three places a content scan cannot see: an ACF image field on a page, a WooCommerce product gallery, and a widget or the site icon.
  2. Make sure it is referenced nowhere else — not in post content, not as a featured image.
  3. Run the full scan.
  4. Look for that file in the unused list.

So there you have it. Let this page be the yardstick for any media cleaner out there, this one included. If a tool cannot find that one image, it is guessing — and a guess is not something you delete on.


More of the same, from the jobs that made me build things, on the blog. The library audit that started this one — three files in five said Unattached, and almost none of them were unused — is written up separately.