Skip to content

Media: Keep non-image uploads on the classic path - #15512

Open
adamsilverstein wants to merge 8 commits into
WordPress:trunkfrom
adamsilverstein:fix/csm-classic-non-image-uploads
Open

adamsilverstein wants to merge 8 commits into
WordPress:trunkfrom
adamsilverstein:fix/csm-classic-non-image-uploads

Conversation

@adamsilverstein

Copy link
Copy Markdown
Member

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

Item 2 on the ticket: page builders that allow SVG or JSON in their media frames had those uploads rejected in the browser once the frame went through the client-side pipeline.

Claude worked through this one, summary below:

Only images the client-side pipeline can process (JPEG, PNG, GIF, WebP, AVIF and HEIC/HEIF) are routed to it now. Every other file - audio, video, PDF, SVG, JSON, archives - is left to plupload and uploads through async-upload.php as it did in 7.1.

Before this, partitionBatch() kept only audio on the classic path, so everything else went to the pipeline even though it does no processing for those files. The pipeline checks file types against allowedMimeTypes, which is get_allowed_mime_types() captured when the page rendered, while the classic path checks the uploader's own filters.mime_types. Plugins extend the latter for a single frame (Elementor appends svg and json to _wpPluploadSettings.defaults.filters.mime_types, and only allows them on the server when its uploadTypeCaller param is present), so those uploads failed with "Sorry, you are not allowed to upload this file type." before reaching the server.

HEIC files are also matched by extension, since a browser on a platform without a HEIC codec may give them no type.

Testing

  • New e2e test in media-library-client-side-upload.test.js: extends the uploader's filter to allow svg, uploads sample.svg, and checks it goes to async-upload.php with no REST request and no "not allowed" error. Fails on trunk (no classic request is made), passes with the fix.
  • media-library, media-new and media-modal client-side upload specs all pass locally in Chrome with DIP active (41 tests).

Manual testing:

  1. In Chrome 137+ over HTTPS or localhost, open Media > Library in grid mode.
  2. Upload a PDF: it should go to async-upload.php (Network tab), not /wp/v2/media.
  3. Upload a JPEG: it should still go through /wp/v2/media, /sideload and /finalize.
  4. With a plugin that allows SVG for its own media frame (e.g. Elementor with unfiltered uploads enabled), upload an SVG through that frame: it should upload instead of showing the "not allowed" error.

AI Use

Code and description both drafted with 🤖 Claude Code from the Elementor report. I will review and test.

🤖 Generated with Claude Code

https://claude.ai/code/session_018vwYjTNtbdhcmnECA2bZxP

adamsilverstein and others added 2 commits November 5, 2026 09:41
A plugin can extend the uploader's file type filter for a single
frame, as page builders do for SVG and JSON. The client-side pipeline
validated those files against the mime types the page was rendered
with, and rejected them in the browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjTNtbdhcmnECA2bZxP
Only route images the client-side pipeline can process (JPEG, PNG,
GIF, WebP, AVIF and HEIC/HEIF) and leave every other file to plupload.
The pipeline does no processing for other files, and its type check
ignored the uploader's own filters, so file types a plugin allows for
a single frame were rejected before reaching the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjTNtbdhcmnECA2bZxP
@github-actions

github-actions Bot commented Nov 5, 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.

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

Props adamsilverstein, desrosj, andrewserong.

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


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

github-actions Bot commented Nov 5, 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.

*/
function isPipelineFile( file ) {
return (
/^image\/(?:jpeg|png|gif|webp|avif|heic|heif)$/i.test(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry a bit about hard coding this list and maintenance (eg. when we add jxl support this will need updating). That said, we already do this in several places in media. I'dd see if we can dedupe this a bit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started addressing this in 1654c43 by consolidating the current core side logic. I'll follow up with a Gutenberg side Issue/PR to add something like isClientSideProcessable( file ) and then have core use that directly, that would move all the logic into one place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it isn't too bad to have a hard-coded list of file types that go through the client-side flow. While it requires manual updating, I imagine we'd be updating it quite infrequently?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, its rare we need to update these hardcoded type lists! That said, I know in the past when we added support for AVIF we forgot some places to add it and only realized when a bug report was filed. Adding the inline commenting saying where the related items need to stay in sync mostly resolves this for me, but if we can move to a single point of truth thats even better!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree with you there! 🙂

The formats were listed twice, once to route files to the pipeline and once to type the media frame's placeholder tiles, so adding a format (eg. JPEG XL) meant finding both. Both now read one list in media-upload-pipeline.js, exposed to media-frame-upload.js as `wp.mediaUploadPipeline.getImageSubtype()`, with a pointer to the upload-media constants it mirrors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjTNtbdhcmnECA2bZxP

@andrewserong andrewserong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix! This is testing well for me with the popular Safe SVG plugin to allow svg uploads. Regular images (e.g. jpegs, etc) continue to be processed via client-side processing, and SVGs upload correctly on their own:

Image Image

LGTM!

@adamsilverstein
adamsilverstein removed the request for review from lancewillett November 6, 2026 01:40

@desrosj desrosj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small comment changes. I had some small test suggestions, but I think that the tests may change based on another finding.

It seems that the same bug can still hit image types.

The root cause of this is the client-side check against allowedMimeTypes, which is captured when the page renders. That check still applies to images.

A brief summary from Claude:

Suppose a plugin allows an image type (AVIF or WebP), only for its own frame or only when a request parameter is present. It doesn't add that type to upload_mimes at render time. That upload will still fail in the browser with "not allowed to upload this file type."

Claude suggested making a file classic any time the type is not in Object.values( settings.allowedMimeTypes ). The server would then decide, which closes the whole class of bug rather than only the non-image half. I'm not sure if that's in the spirit of how the feature was intended to work, though.

Co-authored-by: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com>
@adamsilverstein

Copy link
Copy Markdown
Member Author

It seems that the same bug can still hit image types.

I'm not sure that is right, but I will check!

Suppose a plugin allows an image type (AVIF or WebP), only for its own frame or only when a request parameter is present. It doesn't add that type to upload_mimes at render time. That upload will still fail in the browser with "not allowed to upload this file type."

Those two types are already allowed, maybe JXL would be a better test case. How does it add that type, I assume using the mime_types or https://developer.wordpress.org/reference/hooks/mime_types/ or upload_mimes - https://developer.wordpress.org/reference/hooks/upload_mimes/.

It doesn't add that type to upload_mimes at render time.

when does it add it? is this a bug or a documentation gap?

@adamsilverstein

Copy link
Copy Markdown
Member Author

I'm having claude do some testing on this and research via https://veloria.dev/ on existing plugin use. I'll run tests on adding and removing mime type support to verify they are handled correctly in the client side uploader in gutenberg and the media library.

@adamsilverstein

Copy link
Copy Markdown
Member Author

Thanks @desrosj, good catch - this does reproduce, so I'm adding your suggestion to the PR.

Claude tested this against real plugins, short version:

  • Repro: a test plugin removes webp from upload_mimes unless its own request param is present, and adds webp to the uploader's filter. On the PR head the WebP is rejected in the browser; with client-side processing off (7.1), it uploads.
  • Unlikely in practice: the pipeline only takes formats core allows by default, and in 7.1 the classic uploader's filter came from the same render-time list. Of about 1,150 plugins with upload_mimes filters (searched with https://veloria.dev/), none match this pattern. The per-request ones (eg. Elementor) only add non-image types, which this PR already sends to the classic uploader.
  • Real plugins still work on the PR head: WP Add Mime Types (JXL), SVG Support, the Elementor 4.3.6 editor (SVG), and restriction plugins (WP Add Mime Types -webp, Disallow PNG). Restricted types are still blocked by plupload before the pipeline sees them.
  • Your fix: sending a file to the classic uploader when its type isn't in allowedMimeTypes fixes the repro and leaves the scenarios above unchanged.

I also had Claude record some testing screencasts:

03-review-scenario-pr-head.mp4
05-review-scenario-suggested-fix.mp4

adamsilverstein and others added 2 commits November 5, 2026 23:29
A plugin can allow an image type only for its own uploads, leaving it
out of the allowed types the page was rendered with. The pipeline
rejected those images in the browser before the server could decide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjUDjTr7YBnLJwEFAc6
Send a file to plupload when its type is missing from the allowed
types the page was rendered with, so the server decides, as it did
before the pipeline. Plupload's own filter runs first, so this only
affects files a plugin let through on purpose.

Props desrosj.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjUDjTr7YBnLJwEFAc6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants