WP-CLI
[Pro feature] Two commands, both read-only. They run the same scan the Media → Usage screen runs — the same detectors, the same stored results, the same cursor — so a site scanned from the command line reads identically in wp-admin, and the two can never disagree.
There is no delete verb, deliberately. A non-interactive delete across many sites is exactly the shape this plugin refuses to take. Deletion stays in the admin, behind a confirmation, with its re-verification pass. Nothing here schedules anything either — a scan happens when someone runs one.
wp freshet-unusedmedia scan
[Pro feature] Scans the whole media library and reports the totals.
wp freshet-unusedmedia scan [--resume] [--format=<format>]
| Option | |
|---|---|
--resume |
Continue an interrupted scan from its cursor instead of starting over |
--format=<format> |
table (default), json, csv, yaml |
It runs to completion in one invocation — the browser scan batches because a web request has a time limit, and this does not. Attachment IDs are still fetched in chunks so a large library never lands in memory at once, and the cursor is written after every chunk, so an interrupted run picks up with --resume.
Without --resume, the scan starts from the beginning.
wp freshet-unusedmedia scan
wp freshet-unusedmedia scan --format=json
wp freshet-unusedmedia scan --resume
The summary carries total, scanned, used, unused and unscanned. In table format it ends with a line saying how many files were scanned, how many are unused, and that nothing was deleted. A progress bar is shown for table and suppressed for every machine format, so piped output stays parseable.
Remember what a full scan costs on a large library — hours, not minutes. Budget for that before putting it in front of anything that waits on it.
wp freshet-unusedmedia list
[Pro feature] Lists the files the last scan found unused. It reads the stored results; it does not re-scan. Files in the trash are excluded, exactly as they are on Media → Usage.
wp freshet-unusedmedia list [--fields=<fields>] [--limit=<n>] [--format=<format>]
| Option | |
|---|---|
--fields=<fields> |
Comma-separated columns. Default id,file,title,mime,uploaded,bytes,scanned_at |
--limit=<n> |
Stop after this many files. 0 (default) lists them all |
--format=<format> |
table (default), json, csv, yaml, ids, count |
wp freshet-unusedmedia list
wp freshet-unusedmedia list --format=json
wp freshet-unusedmedia list --format=count
wp freshet-unusedmedia list --fields=id,file,bytes --limit=50
An empty result is a result, not a message: only table says it in words. Anything being piped gets an empty list of the shape it asked for, so a site with nothing unused parses like every other site.
Across many sites
Both commands take WP-CLI's own global flags, so an estate loop is ordinary shell:
for site in /var/www/*/public; do
wp --path="$site" freshet-unusedmedia scan --format=json
done
A --format=json scan followed by list --format=json gives you the whole picture per install without opening a browser. What you then do with it is your call — nothing on this page deletes.
Without a license
The commands are not registered as a free feature and they say so rather than failing obscurely:
This command needs a license key. Scanning, the unused list and deletion are free on Media → Usage; a license adds the Used view, this command, the exportable evidence report and the space totals. Add a key on Media → Usage.
It exits non-zero, and it makes no network request doing so. In the wordpress.org build the command file is not present at all.
Next: Extending.