Bulk actions hide or repeat per-document error messages (Edit many drops them; Delete/Publish/Unpublish repeat them)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 79/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- nextjs, react, typescript
- Domain
- developer-experience, frontend
Research direction
Read packages/ui/src/forms/Form/index.tsx and the bulk action components under packages/ui/src/elements/, especially DeleteMany/index.tsx and the PublishMany, UnpublishMany, and RestoreMany equivalents. Trace how each handles json.message and json.errors, then find the related UI tests. Verify that public reasons appear once, all-fail publish actions show the headline, and partial failures still refresh the list while leaving the drawer behavior appropriate.
Written by the indexing model from the issue text.
Description
Describe the Bug
When a bulk operation fails for some or all documents, the REST response carries both a headline and the per-document reasons:
HTTP 400
{ "docs": [],
"errors": [
{ "id": "a", "isPublic": true, "message": "You can't edit this page." },
{ "id": "b", "isPublic": true, "message": "You can't edit this page." }
],
"message": "Unable to update 2 out of 2 Pages." }
The admin UI handles this badly in three places:
- Edit many drawer drops the reasons.
packages/ui/src/forms/Form/index.tsxshowsjson.messageand returns before readingjson.errors. The editor sees only "Unable to update 2 out of 2 Pages." and never learns why. This applies to Save, Save Draft and Publish in the drawer. - Bulk Delete, Restore and Empty trash repeat identical reasons.
DeleteManyand its siblings build the toast description withjson.errors.map((error) => error.message).join('\n'). When N documents fail for the same reason, the description repeats it N times. - Bulk Publish and Unpublish open one toast per document when every document fails. The all-fail branch calls
errors.forEach((error) => toast.error(error.message)), so N identical toasts open and the "Unable to update…" headline never shows.
A related point: a partial failure (1 of 3 refused) also returns 400. The Edit many form then treats it as a failure, so the drawer stays open and the list does not refresh, even though 2 documents were updated.
Link to the code that causes this issue
packages/ui/src/forms/Form/index.tsx(if (json.message) { errorToast(json.message); return })packages/ui/src/elements/DeleteMany/index.tsx(json.errors.map((error) => error.message).join('\n')), plus the PublishMany, UnpublishMany and RestoreMany equivalents
Reproduction Steps
- Add a
beforeChangehook to a collection that throwsnew APIError("You can't edit this page.", 403, null, true)for a non-admin user. - As that user, select 2 documents in the list view and open Edit many. Change a field and save. Only "Unable to update 2 out of 2 …" appears.
- Select 2 documents and use Delete (with an equivalent
beforeDeletehook). The description repeats the reason twice. - Select 2 documents and use Publish. Two identical toasts open, and no headline appears.
Suggested fix
- Form: when
json.messageis present andjson.errorshas public messages, append the unique ones to the toast. Single-document saves return no top-levelmessage, so they are unaffected. - Bulk toasts: dedupe the messages, e.g.
[...new Set(errors.map((e) => e.message))].join('\n'). - Publish/Unpublish all-fail branch: show the headline with the deduped reasons as the description, as the partial-fail branch already does.
We patch this locally and are happy to open a PR.
Which area(s) are affected?
area: ui
Environment Info
Payload: 3.91.0
@payloadcms/ui: 3.91.0
Node.js: 24
Next.js: 16.3
The same code is still on main (v4.0.0-beta.2) and on 3.x.
This issue was found and reported with the help of Claude Code.
- Dominant language
- TypeScript
- Stars
- 45.5k
- Forks
- 4.3k
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 302
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from payloadcms/payload
-
WhereBuilder throws "Cannot read properties of undefined (reading 'value')" when the list URL filters on a field that is not filterablePossibly taken A pull request linked to this issue is open or already merged. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19002 · 1 comment ·
Maintainers usually reply within 1 day
-
Regression: `findByID` with `disableErrors: true` throws `NotFound` for null/empty ids since 3.91.0Possibly taken @Krrishkumar125 claimed this 6 days ago. Openarea: core Bug invalid-reproduction status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
payloadcms/payload#18935 · 2 comments ·
Maintainers usually reply within 1 day
-
bug: Local API call with req resets req.fallbackLocale to defaultLocalePossibly taken A pull request linked to this issue is open or already merged. Openarea: core Bug created-by: Contributor
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#18768 ·
Maintainers usually reply within 1 day
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collectionsPossibly taken A pull request linked to this issue is open or already merged. OpenBug plugin: multi-tenant status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
payloadcms/payload#18690 ·
Maintainers usually reply within 1 day
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)Possibly taken @PINYOPATTANAWASANPORN claimed this 14 days ago. Openarea: core Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#18674 · 2 comments · 1 reaction ·
Maintainers usually reply within 1 day
All issues in payloadcms/payload
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
keptinventory/kept#336 ·
Maintainers usually reply within 1 day
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
hemi-earn
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
vetro-protocol/vetro-monorepo#1013 ·
Maintainers usually reply within 1 day