payloadcms / payloadcms/payload

Bulk edit (EditMany) save strips all query params from the URL — then overwrites the user's saved column preferences

Open
#17,667 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

Two behaviours combine into silent preference loss:

1. The EditMany success handler rebuilds the URL from an empty params source.

// @payloadcms/ui/dist/elements/EditMany/DrawerContent.js (3.87.0, ~line 287)
const onSuccess = () => {
  router.replace(qs.stringify({
    ...parseSearchParams(searchParams),   // <- comes up EMPTY in the drawer context
    _r: Date.now(),
    page: selectAll ? '1' : undefined
  }, { addQueryPrefix: true }));
  ...

We instrumented history.replaceState and fetch in the browser while performing a bulk save on a list whose URL was
?depth=1&columns=[...]&limit=10. The captured rewrite was:

replaceState → /admin/collections/media?_r=1785992839391&page=1

Every existing query param — including columns= — is dropped. The useSearchParams() value spread into the new URL is empty at the time the drawer's onSuccess runs, even though window.location.search still carries the full query string at that moment (verified in the same capture).

2. The List server view persists URL columns into preferences on every render (@payloadcms/next views/List/index.jsupsertPreferences({ columns: columnsFromQuery, ... })). So once the URL has been stripped and the client re-syncs its (now default) column state into the URL, the stale column set is written over the user's saved preferences. Depending on render timing the visible column loss either self-heals or becomes permanent.

The result for editors: bulk-editing documents randomly destroys their saved column configuration. Bulk edit is a core workflow for media libraries, so this fires often.

Reproduction steps

  1. In any list view, add a non-default column via the Columns panel (URL now carries columns=[...]).
  2. Select several documents → Edit → set any field → Save.
  3. Observe the URL after the drawer closes: ?_r=<timestamp>&page=1 — all other params gone.
  4. Repeat a few times (timing-dependent): the added column disappears from the table, and the collection-<slug> preference record no longer contains it.

Expected / Actual

  • Expected: a bulk save refreshes the list without touching the URL's other query params or the user's saved preferences.
  • Actual: the save rewrites the URL bare; the stripped state can then be persisted over saved preferences.

Proposed fix

Build the post-save URL from the live location rather than the hook value:

  router.replace(qs.stringify({
-   ...parseSearchParams(searchParams),
+   ...Object.fromEntries(new URLSearchParams(window.location.search)),
    _r: Date.now(),
    page: selectAll ? '1' : undefined
  }, { addQueryPrefix: true }));

window.location.search provably still carries the full query at save time (same capture). We've been running this change in production via a pnpm patch; bulk saves now preserve columns/filters/limit. (A deeper fix might address why useSearchParams() is empty in the drawer's render context, and/or make the List view's preference upsert less trusting of URL params.)

Environment

  • Payload: 3.86.0 (found), verified still present in 3.87.0 source
  • @payloadcms/db-postgres, @payloadcms/next
  • Next.js: 16.2.11
  • Node: 22.14.0
  • pnpm 10.30.3, macOS / Vercel

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read @payloadcms/ui/dist/elements/EditMany/DrawerContent.js around the EditMany success handler, then inspect @payloadcms/next/views/List/index.js and reproduce the bulk-save flow with custom columns, filters, and a limit in the URL. Done means the post-save URL preserves existing query parameters and the saved collection preferences are not overwritten by stripped column state.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.