Skip to the document
DocsThe media library

Duplicates, and setting things aside

VergeLabs Library → Duplicates.

Two features live here. One finds copies of the same picture. The other lets you retire a file without deleting it.


Finding duplicates, two ways #

An md5 of the file finds the byte-identical re-upload — the same file dragged in twice, logo.png and logo-1.png.

A dHash finds the same photograph exported again at a different size or quality, where the bytes differ completely. The picture is reduced to 9×8 greyscale pixels and each pixel is recorded as brighter-or-not than the one beside it, giving a 64-bit fingerprint stored as 16 hex characters. Two images within a Hamming distance of 5 are treated as near-duplicates.

Both hashes are stored in attachment meta (_vergeml_hash) so the comparison is done once.

What the dHash deliberately skips #

A 9×8 reduction answers confidently and wrongly for some images, so those are excluded from near-duplicate matching rather than reported as false pairs:

  • images smaller than 64 pixels on a side — icons, spacers, tracking pixels
  • fingerprints with too little variation across them: a flat colour, a plain gradient, a blank scan. One of them looks like every other at that resolution.

The md5 path still catches those when they really are identical.

Tidying #

When you tidy a duplicate set, the plugin picks the copy to keep by asking what is actually pointing at each one:

  1. The copy something references wins.
  2. If nothing references any of them, the oldest wins.

The rest are set aside, not deleted.

Repointing #

Deleting a duplicate yourself, from this screen, repoints everything that used it onto the survivor:

Where a reference can hideHow it is repointed
Post content — <img>, wp-image-N classesURL stem and id replaced
Featured images_thumbnail_id updated
URL-bearing post metaURL stem replaced
Builder layouts — _elementor_data, _fl_builder_dataURL stem and "id":N in their JSON
WooCommerce product galleries_product_image_gallery, a comma list of ids
ACF image fieldsFields that store a bare id

That list is the honest one. A reference in a place none of those covers — a hard-coded path in a theme file, for instance — is not repointed, which is one more reason the default action is to set aside rather than delete.

The scan #

Hashing runs in batches of 25 attachments, capped at 200 per request, so it cannot time out on shared hosting. It reports progress and resumes where it stopped.


Set aside, which is not deleting #

WordPress hard-deletes attachments. wp_delete_attachment() unlinks the file, and MEDIA_TRASH is off by default. So "this file appears unused" plus a delete button is a permanent action taken on the strength of a scan that can only ever say I did not find a reference in the places I looked.

This feature exists so that button does not.

Setting aside is a mark, not a move. The file stays exactly where it is — same bytes on disk, same URL, same attachment id. It is hidden from the media library and listed on a screen of its own. Anything still using it goes on working. If a theme the scan cannot read is showing that photo, setting it aside breaks nothing and you find out.

Nothing may be considered for removal for thirty days. That is a floor, not a setting. The evidence for "unused" is absence, and absence is disproved by somebody noticing, which takes weeks rather than minutes.

There is a manifest. Every file set aside, with its id, title, path, size, and the reason it was set aside — downloadable as a file. A list you can check against a backup is the difference between a decision and a leap.

Taking something back is one click, always, at any point.

There is no delete #

The feature contains no call to wp_delete_attachment(), no unlink(), and no file removal of any kind. There is no delete button and no delete endpoint. When thirty days are up, the screen says the file is now eligible and hands you the manifest. Removing media stays something you do yourself, in WordPress, having looked.

You can verify this: the file is core/quarantine.php in the plugin source, and the plugin is GPLv2.

KeyWritten byHolds
_vergeml_hashDuplicate scanmd5 and dHash
_vergeml_unusedLibrary scan1 if no reference was found
_vergeml_used_inLibrary scanThe post ids that reference the file
_vergeml_filesizeLibrary scanSize in bytes, for Large files
_vergeml_quarantinedSet asideTimestamp it was set aside
_vergeml_quarantine_whySet asideThe reason recorded at the time

See Data model.

Written from version 3.15.0 of the plugin. Last updated 2026-10-02. All documentation · Legal · Support