[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