Skip to content

Client-side media: Original full-size image not uploaded when big image threshold scaling applies #75320

Description

@adamsilverstein

Description

Description

When using client-side media handling (the "Client Side Media" experiment), uploading a large image that exceeds the big_image_size_threshold (default 2560px) correctly creates and uploads a -scaled version, but does not upload the original full-size image to the server.

This differs from server-side WordPress behavior, where both the original file and the -scaled file exist on disk after upload.

Expected behavior

When a large image (e.g., 4000x3000) is uploaded with client-side media enabled:

  1. The -scaled version should be uploaded as the main attachment (this works correctly today)
  2. The original full-size image should also be uploaded/sideloaded to the server
  3. The attachment metadata should include original_image pointing to the original file on disk

This matches WordPress core's server-side behavior in wp_create_image_subsizes(), where both files are retained on the server.

Current behavior

Only the -scaled version is uploaded to the server. The original image is kept in browser memory (as item.sourceFile) and used for thumbnail generation, but is never sent to the server. As a result:

  • The original file does not exist on the server filesystem
  • The original_image metadata may be missing or incorrect
  • Plugins and features that depend on the original file being present (e.g., "Edit Original Image", thumbnail regeneration plugins) will not work as expected

Screenshots

Server-side handling (both original and scaled exist on disk):
scaled

Client-side media handling (only scaled exists):
client side scaled

Note: the generated sub-sizes match correctly between both approaches.

Technical details

In packages/upload-media/src/store/private-actions.ts, the prepareItem action queues a ResizeCrop operation with isThresholdResize: true followed by an Upload. The resize creates the -scaled file in resizeCropItem, which replaces item.file. The subsequent upload sends only this scaled file.

The original image is preserved in item.sourceFile and is used as the source for thumbnail generation in generateThumbnails, but it is never sideloaded to the server as a separate file.

Proposed fix

After the initial -scaled upload completes, sideload the original full-size image to the server with image_size: 'original'. The sideload endpoint (class-gutenberg-rest-attachments-controller.php) already handles this case and stores the reference in metadata['original_image']:

if ( 'original' === $image_size ) {
    $metadata['original_image'] = wp_basename( $path );
}

The fix would add a sideload step in generateThumbnails (or uploadItem) for the original sourceFile when the image was threshold-scaled, similar to how the -rotated version is already sideloaded for images needing EXIF rotation.

Related

Step-by-step reproduction instructions

  1. Enable Gutenberg > Experiments > Client Side Media
  2. Upload an image larger than 2560px in either dimension (alternately use the big_image_size_threshold filter to use smaller images)
  3. Check the uploads directory on the server — only the -scaled file is present; the original is missing
  4. Compare with the same upload when the experiment is disabled — both the original and -scaled files are present

Screenshots, screen recording, code snippet

No response

Environment info

No response

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Feature] Client Side MediaMedia processing in the browser with WASM[Type] BugAn existing feature does not function as intended

    Type

    No fields configured for Bug.

    Projects

    Status
    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions