Skip to content

REST API: Always register the media url argument - #12833

Open
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:fix/65517-register-url-arg
Open

REST API: Always register the media url argument#12833
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:fix/65517-register-url-arg

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Aug 4, 2026

Copy link
Copy Markdown
Member

Addresses @jeremyfelt's question in https://core.trac.wordpress.org/ticket/65517#comment:14 about url, generate_sub_sizes, and convert_format being processed on POST /wp/v2/media even when they aren't registered in the schema.

They needed fixing in opposite directions, which is what this does.

url should work either way. Sideloading an external image works around a cross-origin fetch the browser can't make, and that fails whether or not client side media processing is enabled - cross-origin isolation makes it necessary, not conditional. As far as I can tell the feature doesn't depend on client side media processing at all, so the argument is now registered unconditionally.

Worth being clear that leaving it unregistered was never disabling it. create_item() reads the parameter either way, so the sideload already ran on sites without the feature, just without the sanitize_url and wp_http_validate_url() callbacks the registered argument carries. An unsafe URL came back as a bare http_request_failed instead of a 400. Registering it always is what actually closes that.

generate_sub_sizes and convert_format go the other way. They hand image processing to the client, and the /media/<id>/sideload route the client uploads the results to is only registered when the feature is on. Honoring them otherwise leaves an attachment with no sub-sizes and no way to add them, and generate_sub_sizes of false also relaxes the unsupported image type check in create_item_permissions_check() on a site that never opted in. Both are now ignored unless client side media processing is enabled.

Trac ticket: https://core.trac.wordpress.org/ticket/65808

How has this been tested

npm run test:php -- --filter 'WP_Test_REST_Attachments_Controller' --group restapi
  • Four new tests with client side media processing disabled: url is registered while the other two aren't, sideloading works, an unsafe URL is rejected with a 400, and generate_sub_sizes of false is ignored so sub-sizes are still generated. Three of the four fail on trunk without the change - I checked by stashing the source change and re-running. The fourth is a regression guard, since the sideload already worked on trunk, just unvalidated.
  • Full WP_Test_REST_Attachments_Controller class: 192 tests, 2 skipped, 0 failures.
  • phpcs --standard=phpcs.xml.dist clean on both changed files.
  • wp-api-generated.js needs no update. The fixture is generated with client side media processing enabled, and in that configuration the registered arguments are unchanged.

Types of changes

  • Register the url argument unconditionally in get_endpoint_args_for_item_schema().
  • Ignore generate_sub_sizes and convert_format in create_item() and create_item_permissions_check() unless client side media processing is enabled.
  • Add tests for both, and a disable_client_side_media_processing() test helper.

Note on an existing test

test_upload_unsupported_image_type_skipped_when_not_generating_sub_sizes (from #64836) called the permissions check with generate_sub_sizes of false without enabling client side media processing, so it was asserting exactly the behavior this changes. It now enables the feature first, which keeps its original intent - the comment on it already says "when the client handles image processing" - but flagging it since it's an existing test from another ticket. @jeremyfelt @andrewserong does that read right to you?

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Drafting the implementation and tests from the review notes on the ticket. I reviewed and verified the behavior and test results myself.

Sideloading an external image with the `url` parameter on `POST /wp/v2/media` works around a cross-origin fetch the browser cannot make. That fetch fails regardless of whether client-side media processing is enabled, so the argument was registered too narrowly.

Leaving the argument unregistered did not disable it. `create_item()` reads the parameter either way, so on a site without client-side media processing the sideload still ran, but without the `sanitize_url` and `wp_http_validate_url()` callbacks the registered argument carries, and an unsafe URL returned a bare `http_request_failed` rather than a 400. Register `url` unconditionally so its validation always applies.

The `generate_sub_sizes` and `convert_format` parameters are different: they hand image processing to the client, and the route the client uploads the results to is only registered when the feature is enabled. Honoring them otherwise would leave an attachment with no sub-sizes and no way to add them, and would relax the unsupported image type check in `create_item_permissions_check()` on a site that never opted in. Both are now ignored unless client-side media processing is enabled.

Follow-up to [62659].

See #65517.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein, andrewserong, jeremyfelt.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@andrewserong

Copy link
Copy Markdown
Contributor

Ignore generate_sub_sizes and convert_format in create_item() and create_item_permissions_check() unless client side media processing is enabled.

the comment on it already says "when the client handles image processing" - but flagging it since it's an existing test from another ticket. @jeremyfelt @andrewserong does that read right to you?

I'm a little on the fence about this one. In terms of the REST API itself, for the behaviours of switching off conversion or generating sub-sizes, do we care if client-side media processing is enabled? Is whether client-side media processing is enabled orthogonal to the behaviour here?

I don't mind too much either way, I'm mostly thinking about how to keep things simple.

@jeremyfelt

Copy link
Copy Markdown
Member

@adamsilverstein @andrewserong 👋🏻 Sorry for the late flag, I'm still wrapping my head around bits here. Here's my understanding of the paths that we're adding in 7.1.

First, there is a new "Upload to Media Library" button in the editor that appears when you have image block markup like this:

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://example.test/external/1234.jpg"></figure>
<!-- /wp:image -->

This submits a payload to the wp/v2/media endpoint with post and url attributes. It does not attempt to do anything with client side media processing.

This can also be fired as part of a pre-publish check suggesting that external media be uploaded.

Second, when a supported image is uploaded by the user in a supported browser and true === wp_is_client_side_media_processing_enabled(), the resizing is handled in the browser and associated with multiple requests:

  1. One request to the wp/v2/media endpoint with file (image binary), post (post ID), and generate_sub_sizes (false) attributes.
  2. Multiple requests to the wp/v2/media/<id>/sideload endpoint with file (image binary), image_size, and convert_format (false) attributes.
  3. One request to the wp/v2/media/<id>/finalize endpoint with sub_sizes as an array of data about the generated image sizes.

And then third, as a side effect of adding those features, the ability for extenders to make calls to these endpoints:

  1. wp/v2/media with the url attribute (and generate_sub_sizes if client side processing enabled)
  2. wp/v2/media/<id>/sideload (if client side processing enabled)
  3. wp/v2/media/<id>/finalize (if client side processing enabled)

Conclusions

It seems like the WordPress core editor side of things is working as expected, but I think there are a handful of other things that can be cleaned up or clarified before folks discover the endpoints and start using them. It will be harder to correct in the future and maintain back-compat.

  1. Return an error if both file and url attributes are sent to wp/v2/media at the same time.
  2. Ensure fields like title, alt_text, caption, etc.... work when a url attribute is provided.
  3. Fire rest_pre_insert_attachment and rest_insert_attachment for requests where url attribute is provided. (And add a check for is_wp_error() on the pre-hook.)
  4. Call wp_after_insert_post() to fire wp_after_insert_post. (A comment in create_item_from_url() says it's handled, but attachments are returned too early in wp_insert_post())

Items 2, 3, and 4 come from a lack of parity between create_item() and create_item_from_url(). It may be helpful to merge those methods—or rather than return early, continue with create_item() after create_item_from_url() handles the initial sideload.

@adamsilverstein

Copy link
Copy Markdown
Member Author

Items 2, 3, and 4 come from a lack of parity between create_item() and create_item_from_url(). It may be helpful to merge those methods—or rather than return early, continue with create_item() after create_item_from_url() handles the initial sideload.

That makes sense, we would want the usual create_item pathway to fire as usual, as if the user had upload the file directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants