Opened 5 weeks ago
Last modified 4 days ago
#65053 assigned defect (bug)
Media upload from the post edit screen shows an inaccurate number when uploading multiple media files
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.1 | Priority: | normal |
| Severity: | normal | Version: | 6.3 |
| Component: | Upload | Keywords: | has-patch has-test-info has-unit-tests commit |
| Focuses: | ui, javascript | Cc: |
Description
When uploading multiple media files from the Edit Post screen via “Set featured image”, it displays incorrect numbers.
Steps to reproduce:
- Create a new post.
- Click on “Set featured image.”
- Go to “Upload files” and upload multiple files.
- Notice that the numbers are inaccurate (you may need to repeat step 3 multiple times to reproduce the issue).
Attachments (4)
Change History (22)
#1
@
5 weeks ago
Thanks for the bug report @Dharm1025 - I was able to reproduce this immediately in the editor. I cleared my media library, then dragging 10 files onto the editor the display read "Showing 10 of 11".
I was not able to reproduce in the media library itself, these seems specific to the editor. I was also not able to reproduce it using the gallery inserter or by dragging a group of images onto the editor. It seems tied to invoking the uploader from the "Set Featured Image" button as you described.
It feels a bit buggy that you can select more than one image at all from this upload dialog, only one image can be the featured image. So maybe the bug here is the inserter should only allow single image selection (similar to the image block inserter). Then you would never see the mismatched count your report identified.
This may need a Gutenberg PR (and matching issue) to fix, depending on the exact issue.
#2
@
5 weeks ago
@Dharm1025 if possible, can you verify that https://github.com/WordPress/gutenberg/pull/77213 fixes your issue?
#3
@
5 weeks ago
I tested the PR manually and verified I can only select a single image from the featured image button. Are you seeing this problem elsewhere?
#4
@
5 weeks ago
My investigation on Playground revealed that this issue might have been present since at least WordPress 6.3. For some reason, the block editor and the core media modal may not be interacting correctly.
#5
@
4 weeks ago
Reproduction Report
Description
This report validates whether the issue can be reproduced.
Environment
- WordPress: 6.9.4
- PHP: 8.3.30
- Server: PHP.wasm
- Database: WP_SQLite_Driver (Server: 8.0.38 / Client: 3.51.0)
- Browser: Chrome 147.0.0.0
- OS: Windows 10/11
- Theme: Twenty Twenty-Five 1.4
- MU Plugins: None activated
- Plugins:
- Test Reports 1.2.1
Actual Results
- ✅ Error condition occurs (reproduced): The media library counter (e.g., "Showing X of Y") does not sync correctly with the actual number of uploaded files during the batch process..
Additional Notes
- The issue specifically triggers when the media modal is opened via the "Set Featured Image" button in the post editor.
The mismatch in counting persists even after the upload process is completed until the modal is refreshed.
Supplemental Artifacts
This ticket was mentioned in PR #11585 on WordPress/wordpress-develop by @hbhalodia.
4 weeks ago
#6
- Keywords has-patch added
Trac ticket: https://core.trac.wordpress.org/ticket/65053
## Use of AI Tools
AI assistance: Yes
Tool(s): GitHub Copilot, Claude
Model(s): Claude Sonnet 4.6
Used for: Identifying root cause and for a fix. Have reviwed the final implementation and raised the PR.
# Detailed Claude Analysis and fix
## Root Cause Analysis: "Showing X of Y media items" shows incorrect total count
Trac ticket: https://core.trac.wordpress.org/ticket/65053
Related PR: https://github.com/WordPress/gutenberg/pull/77213
Affected files: src/js/media/models/attachments.js
---
### The Symptom
When opening the media library modal (e.g. via "Set featured image" or an Image block), the footer media count displayed an incorrect total count:
Showing 8 of 7 media items
The displayed count (8) was correct, but the total (7) was one short of the real total.
---
### Background: How the count works
The media library uses a Backbone.js collection architecture with three key classes:
| Class | Role |
|---|---|
wp.media.model.Query | Fetches paginated attachments from the server via AJAX. Stores totalAttachments parsed from the X-WP-Total response header.
|
wp.media.model.Attachments | A client-side collection that can mirror a Query and/or observe additional collections (e.g. the selection).
|
wp.media.model.Selection | Extends Attachments. Holds the currently selected items in the modal.
|
The "Showing X of Y" text in the Load More bar is rendered by AttachmentsBrowser.updateLoadMoreView():
sprintf( __( 'Showing %1$s of %2$s media items' ), this.collection.length, // items currently displayed this.collection.getTotalAttachments() // total on the server )
getTotalAttachments() reads directly from the mirrored Query collection's totalAttachments property:
getTotalAttachments: function() { return this.mirroring ? this.mirroring.totalAttachments : 0; },
The totalAttachments on the Query is updated in real-time via two private methods bound as event listeners:
_addToTotalAttachments: function() { if ( this.mirroring ) { this.mirroring.totalAttachments = this.mirroring.totalAttachments + 1; } }, _removeFromTotalAttachments: function() { if ( this.mirroring ) { this.mirroring.totalAttachments = this.mirroring.totalAttachments - 1; } },
---
### The Root Cause
In wp.media.model.Attachments, the observe() method is responsible for watching another collection and replicating its add/remove/reset events. Before this fix, it also registered the total-count trackers on every observed collection:
// BEFORE (buggy): observe: function( attachments ) { this.observers = this.observers || []; this.observers.push( attachments ); attachments.on( 'add change remove', this._validateHandler, this ); attachments.on( 'add', this._addToTotalAttachments, this ); // ← too broad attachments.on( 'remove', this._removeFromTotalAttachments, this ); // ← too broad attachments.on( 'reset', this._validateAllHandler, this ); this.validateAll( attachments ); return this; },
The critical detail is that observe() can be called on more than just the Query collection. Both wp.media.controller.FeaturedImage and wp.media.controller.ReplaceImage call:
// featured-image.js (line 83) and replace-image.js (line 86): library.observe( this.get('selection') );
This is intentional and correct behaviour for *display* purposes — it ensures that a pre-existing featured image (which might not be in the first page of query results) is always visible in the grid even if the server hasn't returned it yet.
However, it also meant that the total-count handlers were now bound to the Selection collection as well as the Query collection.
#### The event cascade that causes the wrong count
- User opens the "Set featured image" modal. A featured image is already set (ID: 42).
library.observe(selection)is called. The selection contains 1 item (attachment 42).- The server responds with 8 total attachments →
query.totalAttachments = 8. ✅ - User clicks a different image (ID: 99) to set as the new featured image.
Selection.add()is called for attachment 99. Becausethis.multiple === false, it first callsthis.remove(this.models)— removing attachment 42 from the selection.- The
removeevent fires on theSelectioncollection. _removeFromTotalAttachmentsis triggered (it was bound to the selection in step 2) →query.totalAttachmentsis decremented: 8 → 7. ❌Selection.add()completes, adding attachment 99, firingadd→_addToTotalAttachments→ 7 → 8. ✅ (briefly correct again)- But
updateLoadMoreViewis debounced (10ms). If it fires between steps 7 and 8 (or if theaddevent is never fired due to validation), the UI shows "Showing 8 of 7".
Additionally, in the general case, any add/remove on the selection (without a corresponding add/remove on the query) permanently corrupts totalAttachments for the lifetime of the modal session.
---
### The Fix
The total-count event bindings (_addToTotalAttachments / _removeFromTotalAttachments) belong exclusively on the primary query collection being mirrored — not on every additionally-observed collection.
The fix moves these bindings from observe() into mirror(), which is the method that establishes the relationship with the Query collection:
// AFTER (fixed): observe: function( attachments ) { this.observers = this.observers || []; this.observers.push( attachments ); attachments.on( 'add change remove', this._validateHandler, this ); // _addToTotalAttachments / _removeFromTotalAttachments removed from here attachments.on( 'reset', this._validateAllHandler, this ); this.validateAll( attachments ); return this; }, mirror: function( attachments ) { if ( this.mirroring && this.mirroring === attachments ) { return this; } this.unmirror(); this.mirroring = attachments; this.reset( [], { silent: true } ); this.observe( attachments ); // Bind total attachment count tracking only to the mirrored query // collection, not to additional observed collections (e.g. selection). attachments.on( 'add', this._addToTotalAttachments, this ); attachments.on( 'remove', this._removeFromTotalAttachments, this ); this.trigger( 'attachments:received', this ); return this; },
#### Why this is safe
mirror()is only ever called with awp.media.model.Queryinstance (via_requery()). The query accurately reflects server-side counts, so attaching the total-count trackers there is correct.observe()is called for both the query (viamirror()) and supplemental collections like the selection. After this fix, observe() only handles item visibility/validation, which is its original purpose.- The
unmirror()path is unaffected: it callsunobserve(this.mirroring)which calls.off(null, null, this)on the query, removing all handlers bound withthisas the context — including the total-count ones added inmirror(). - There are no other callers of
observe()in core that expect the total-count side effect.
---
### Files Changed
| File | Type | Change |
|---|---|---|
| attachments.js | Source | Moved _addToTotalAttachments/_removeFromTotalAttachments bindings from observe() to mirror()
|
---
### Fix Location: Core, not Gutenberg
Although this bug is reproducible through the "Set featured image" panel in Gutenberg, the root cause is in the legacy media library JavaScript stack in WordPress Core. The Attachments, Selection, and Query Backbone models are part of wp.media which lives in Core (wp-includes/js/). Gutenberg's MediaUpload component is ultimately a wrapper that opens this same modal. Adding multiple={false} to MediaUpload (as proposed in the Gutenberg PR #77213) would address the UX problem of allowing multi-select but would not fix this count bug, which is triggered by any single-selection change in the modal, including image blocks and other media contexts.
#7
@
4 weeks ago
Hi @jadavsanjay @wildworks @adamsilverstein @Dharm1025, Can you please try to reproduce the issue again with the patch added in the PR - https://github.com/WordPress/wordpress-develop/pull/11585
I have asked help from AI (Claude Sonnet 4.6), it was pretty much good to find root cause and explain what was the real issue. After the fix, I tried replicating the issue but was not able to and also checked for regression but there was none.
Thanks,
#8
@
4 weeks ago
Patch Testing Report
Patch Tested: https://github.com/WordPress/wordpress-develop/pull/11585
Environment
- WordPress: 7.1-alpha-62161-src
- PHP: 8.2.29
- Server: nginx/1.29.4
- Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
- Browser: Opera
- OS: macOS
- Theme: Twenty Twenty-Five 1.4
- MU Plugins: None activated
- Plugins:
- Test Reports 1.2.1
Steps taken
- Create a new post
- Click Set featured image
- Go to Upload files and upload multiple files
- Observe incorrect image count (e.g.
Showing 7 of 8 media items) - Apply the patch
- Repeat steps 2-4
- Observe correct image count (e.g.
Showing 7 of 7 media items) - ✅ Patch is solving the problem
Expected result
- While uploading we should see the correct number of images
Screenshots/Screencast with results
@adamsilverstein commented on PR #11585:
2 weeks ago
#10
I confirmed the bug, applied the patch on a branch, and added a QUnit test at the model layer that asserts Attachments#observe(selection) does not corrupt the mirrored query's totalAttachments (and that mirrored-query add/remove still drives the total as a regression guard). It fails on trunk with Actual: 9, Expected: 8 and passes once the bindings move into mirror(). Branch: https://github.com/adamsilverstein/wordpress-develop/tree/trac-65053-attachments-count — feel free to fold the test into this PR.
#11
@
2 weeks ago
@hbhalodia - nice work.
The fix in https://github.com/WordPress/wordpress-develop/pull/11585 looks good! I added some tests in https://github.com/WordPress/wordpress-develop/compare/trunk...adamsilverstein:wordpress-develop:trac-65053-attachments-count if you want to add them to your pr. They fail before the pr changes.
@hbhalodia commented on PR #11585:
12 days ago
#15
Hi @adamsilverstein, I have added the test case as you mentioned in the branch.
Thanks,
@adamsilverstein commented on PR #11585:
5 days ago
#16
Hi @adamsilverstein[[Image(chrome-extension://hgomfjikakokcbkjlfgodhklifiplmpg/images/wp-logo.png)]], I have added the test case as you mentioned in the branch.
Thanks,
Thanks @hbhalodia!



Screenshot