Skip to content

Client-side media processing creates duplicate sub-size files for same-dimension image sizes #77035

@adamsilverstein

Description

@adamsilverstein

Description

When themes register image sizes with the same dimensions as WordPress built-in sizes, client-side media processing creates duplicate physical files on disk. For example, with Twenty Eleven active:

  • medium_large (768x1024) and large (768x1024) → two separate files: IMG-768x1024.jpeg and IMG-768x1024-1.jpeg
  • medium (225x300) and small-feature (225x300) → IMG-225x300.jpeg and IMG-225x300-1.jpeg
  • post-thumbnail (1000x288) and large-feature (1000x288) → IMG-1000x288.jpeg and IMG-1000x288-1.jpeg

Server-side uploads via the media library does not create duplicates.

Steps to reproduce

  1. Activate a theme that registers image sizes matching built-in dimensions (e.g., Twenty Eleven)
  2. Enable client-side media processing (requires compatible browser with SharedArrayBuffer)
  3. Upload a large image (above big image threshold)
  4. Check the uploads directory — duplicate files exist with -1 suffixes

Expected behavior

When multiple registered image sizes have the same effective dimensions (width, height, crop), only one physical file should be created and shared across size names in the metadata.

Root cause

In generateThumbnails (packages/upload-media/src/store/private-actions.ts), the code iterates every entry in missing_image_sizes and creates a separate sideload item for each without checking if multiple size names map to the same dimensions. Each sideload generates a separate physical file (VIPS resize + upload), and the server appends -1 via wp_unique_filename() to avoid filename collisions.

Proposed fix

Deduplicate sizesToGenerate by dimensions before the sideload loop. Group size names by their effective dimensions (width, height, crop) and only generate/sideload one physical file per unique dimension set. The remaining size names in each group should reference the same file in metadata.

const generatedDimensions = new Set<string>();
for ( const name of sizesToGenerate ) {
    const imageSize = allImageSizes[ name ];
    const key = `${imageSize.width}x${imageSize.height}x${imageSize.crop}`;
    if ( generatedDimensions.has( key ) ) {
        continue; // Skip — another size with same dimensions already queued
    }
    generatedDimensions.add( key );
    // ... create sideload item ...
}

Note: This simple approach skips duplicate dimension sizes entirely, which means those size names won't appear in metadata.

Testing

An E2E test should be added that:

  1. Registers custom image sizes with dimensions matching built-in sizes
  2. Uploads an image with client-side processing enabled
  3. Verifies no duplicate physical files are created (no -1 suffix files)
  4. Verifies all registered size names appear in attachment metadata with correct file references

Screenshots

Client-side upload

note duplicate sizes with -1

Image

Server-side upload

No duplicates

Image

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions