[wp-trac] [WordPress Trac] #65329: Move sideload metadata writes to the finalize endpoint

WordPress Trac noreply at wordpress.org
Fri May 22 22:26:49 UTC 2026


#65329: Move sideload metadata writes to the finalize endpoint
-------------------------+-------------------------------------------------
 Reporter:               |      Owner:  adamsilverstein
  adamsilverstein        |
     Type:  enhancement  |     Status:  assigned
 Priority:  normal       |  Milestone:  7.1
Component:  Media        |    Version:
 Severity:  normal       |   Keywords:  has-patch has-unit-tests needs-
  Focuses:               |  testing
-------------------------+-------------------------------------------------
 Backport - part of the client-side media processing feature being re-
 introduced for 7.1 (see #64919, originally #62243).

 When client-side media processing is enabled, the editor generates each
 image sub-size in the browser and sideloads it to the attachment via `POST
 /wp/v2/media/<id>/sideload`. Today each sideload request reads
 `_wp_attachment_metadata`, merges in its own sub-size, and writes the
 whole array back. Because the editor uploads sub-sizes for a single
 attachment concurrently (bounded only by `maxConcurrentUploads`, default
 5), two requests can read the same metadata, each add their own size, and
 the later write clobbers the earlier one. The result is missing sub-sizes
 in the saved metadata.

 The merged Gutenberg PR explored MySQL advisory locks and transient-based
 locks before settling on the approach in this ticket: remove the shared
 write from the per-size request entirely.

 == Proposed change ==

 * `sideload_item()` no longer writes `_wp_attachment_metadata`. Instead it
 returns lightweight **sub-size data** for the size it just processed:
 `image_size`, `width`, `height`, `file`, `mime_type`, `filesize`, and
 `original_image` for the scaled case. The scaled flow still repoints
 `_wp_attached_file` via `update_attached_file()`, which is per-attachment
 and not the contended `_wp_attachment_metadata`.
 * `finalize_item()` accepts a new schema-validated `sub_sizes` array
 argument. The client accumulates the sub-size data returned by each
 sideload response and passes the full set to finalize, which applies them
 all in a single `wp_update_attachment_metadata()` call before firing
 `wp_generate_attachment_metadata`.

 This mirrors how Core itself builds sub-sizes: every size is created
 first, then metadata is written once. Existing `image_meta` (EXIF) on the
 attachment is preserved when finalize merges in the collected sub-sizes.

 == Why this is safe ==

 * The contended write (`_wp_attachment_metadata`) now happens exactly
 once, in finalize, instead of once per concurrent sideload.
 * `sub_sizes` is fully schema-validated in `register_routes()`: per-item
 `image_size` is required, `width` / `height` / `filesize` are positive
 integers, and `mime_type` matches `^image/.*`.
 * The empty-`sub_sizes` path is preserved, so a finalize call with no
 collected sizes behaves exactly as before.

 == Patch ==

 The PHP change is a backport of the merged Gutenberg PR:

 * Gutenberg PR: [https://github.com/WordPress/gutenberg/pull/75888
 WordPress/gutenberg#75888, Upload Media: Enable concurrent sideload
 uploads]
 * Core backport PR: [https://github.com/adamsilverstein/wordpress-
 develop/pull/48 adamsilverstein/wordpress-develop#48]

 Files touched:

 * `src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-
 controller.php`
 * `tests/phpunit/tests/rest-api/rest-attachments-controller.php`

 The JavaScript counterpart (media-utils `sideloadToServer()` returning
 sub-size data, and the client-side accumulation passed to finalize) ships
 through the normal Gutenberg to Core package sync and is not part of this
 PHP patch.

 == Tests ==

 * `test_sideload_scaled_image` updated to assert the new response shape
 (sub-size data, not a prepared attachment response) and that the attached
 file is repointed during sideload while metadata is only written after
 finalize.
 * Added `test_finalize_writes_regular_sub_sizes` (regular size branch),
 `test_finalize_writes_original_metadata` (the `original` branch), and
 `test_finalize_preserves_image_meta` (EXIF `image_meta` survives a
 finalize that adds sub-sizes).
 * Together with the existing `test_finalize_item*` tests, all three
 finalize branches (regular / scaled / original) plus the empty-`sub_sizes`
 path are covered.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/65329>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list