Writing

Freshet Unused Media is on wordpress.org — here is what it refuses to do

· Kristoff Bertram

A media cleaner is judged by what it deletes. This one is built to be judged by what it keeps — so the honest way to introduce it is a list of refusals.

Freshet Unused Media is in the WordPress plugin directory, free, GPL, no account and no key. It answers one question for every file in the media library: does anything on this site still refer to this? It looks in every place a reference can hide — post content, custom fields and their field keys, block and page-builder markup that stores only an ID, options and theme mods, widgets, term and user meta, comments, autosaves — and then shows you what it found, with a link to each place, before it lets you delete anything.

I wrote it because WordPress cannot tell you whether a file is used. The Uploaded to column records the post you had open at upload time and nothing else. On the library that started this, three files in five read Unattached, and a sample said 98% of those were still on the site somewhere. That audit is written up separately; this page is about the plugin.

Try it before installing it

Open it in WordPress Playground — a throwaway WordPress in your browser tab, nothing to sign up for, nothing to install. It boots with the plugin active and a small bakery site already seeded: twenty images, eight of them used the ways a real site uses them (image and gallery blocks, a raw URL in a paragraph, a featured image, the site icon), twelve used nowhere — and five of those twelve attached to a page, which is exactly the signal the plugin exists to correct. It lands on Media → Usage with the scan one click away. Delete whatever you like; the site is gone when you close the tab.

What it refuses to do

Deleting media is permanent by default, so the scan is built to be wrong in one direction only: towards keeping the file. Six rules, all of them in the free plugin.

  1. It refuses to guess. Ambiguity counts as used. A bare attachment ID in a meta key it has never seen keeps the file. Every match is boundary-checked, so attachment 123 is never matched by wp-image-1234 or by photo-1230.jpg.
  2. It refuses to trust a stale answer. Every file is re-checked in the instant before it is deleted. Anything that became used between the scan and your click is skipped, not deleted, and the result says how many were skipped.
  3. It refuses to forget the trash. A reference from a trashed post or comment still counts, because trashed things get restored.
  4. It refuses to touch work in progress. Files uploaded in the last 24 hours count as in use — an editor may still be placing them — so a clean-up never removes the thing somebody is about to publish. (Tunable with freshet_unusedmedia_upload_grace.)
  5. It refuses to summarise. There is no “47 unused files, delete all?” without the list behind it. Every reference is shown with the object it lives in, the field or option it was found in, and a link to go and look. You check the reasoning before you act on it.
  6. It refuses to pretend it can see everything. A plugin that keeps attachment IDs in its own database table — a form builder, a slider, some page builders — is invisible to any query-based scanner, this one included. That blind spot is written into the FAQ on the listing rather than left for you to discover, and there is a filter (freshet_unusedmedia_detectors) to register a detector for your own table that reports back like a built-in one.

And one more that is not a rule so much as the absence of a feature: it deletes nothing on its own. No schedule, no background job. Scanning only reads. Deletion happens when you click a delete action, after re-verification, and not otherwise. If you want a safety net under that, add define( 'MEDIA_TRASH', true ); to wp-config.php and deletions go to the media trash instead of straight to disk.

What that costs

Time. A first full scan of a large library is measured in hours, not minutes, because every file is checked against whole-table searches for each of the reference shapes above. It runs in batches from the Media → Usage screen, each batch stops before the PHP time limit and the next one resumes from the last completed file, so a scan always makes progress and can be paused and picked up later. On the audited library that was about fourteen hours for 3,656 files. A scanner that finishes the same library in ninety seconds has not looked in the places where the references actually were. The detection rules are written out in full in the docs.

What is free, and what is not

Scanning, detection and deletion are the free plugin, and they stay free — nothing in the download is locked, limited or time-barred. Unused Media Pro adds four things for people doing this across many sites: the Used view (every place a file is used, for replacing rather than removing), WP-CLI (wp freshet-unusedmedia scan and list, read back as JSON), an evidence export as CSV or JSON, and space totals. It is EUR 149 once, for every site you build on, with twelve months of updates. None of the four deletes anything — the part that can do damage is the part everyone gets, with the six refusals above built into it.


Install it from wordpress.org, try it in Playground, or read the source. If it keeps a file you were sure was unused, the evidence panel will say why — and if it is wrong about that, I want to know.