payloadcms / payloadcms/payload

Query Presets: whole admin crashes (notNullString.trim is not a function) when a preset filters a hasMany relationship — Postgres numeric IDs

Open
#18,083 3 comments 0 reactions 1 assignee View on GitHub

@r1tsuu is already working on this.

Since Sep 11, 2026.

area: ui Bug invalid-reproduction v3
Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

On Postgres (numeric IDs), saving a Query Preset with a filter on a hasMany: true relationship field stores the raw numeric IDs in the preset's where, e.g.:

{ "or": [{ "and": [{ "authors": { "equals": [5] } }] }] }

When the preset selection list later renders, QueryPresetsWhereCell passes each raw filter value to toWords(), which calls .trim() on it. A number has no .trim, so the render throws and the entire admin white-screens ("Application error: a client-side exception has occurred"). There is no error boundary around the cell, so one bad preset makes the collection's presets unusable for every user who can read it.

Uncaught client error:

Uncaught TypeError: (e || "").trim is not a function      // minified toWords
    at rX (…/chunks/….js:7:94536)                          // toWords
    at ….js:48:134250
    at Array.map (<anonymous>)                             // WhereCell value.map(val => toWords(val))
    ...

Root cause (verified against the published 3.88.0 packages):

  1. packages/ui elements/WhereBuilder/Condition/Relationship/index.tsx: the filter multi-select's onChange emits raw option.values. On Postgres these are numbers (on MongoDB they are strings, which is why this mostly goes unnoticed). Saving the preset persists the numeric array into the preset document.
  2. packages/uielements/QueryPresets/cells/WhereCell/index.tsx (transformWhereToNaturalLanguage, still marked /** @todo: improve this */): for array values it does value.map((val) => toWords(val)).
  3. packages/payloadutilities/formatLabels.ts:
const notNullString = inputString || ''
const trimmedString = notNullString.trim()   // TypeError when inputString is a number

10-second clean-room proof (pristine package from npm, no app, no DB):

npm i payload@3.88.0 pluralize
node --input-type=module -e "
import { toWords } from 'payload/dist/utilities/formatLabels.js';
toWords(5);"
# TypeError: notNullString.trim is not a function

Related context:

  • #12815 covered the "Invalid Date"/notNullString.trim family and is closed, but the numeric-array case above still crashes on 3.88.0.
  • The server-side twin of this bug (the same equals: [5] shape breaking the SQL query with a parameter-count mismatch) was fixed in @payloadcms/drizzle (present in 3.88.0: sanitizeQueryValue now coerces equals/not_equals + array to in/not_in for relationship/upload fields). The client display path was not given the same treatment.
    Suggested fix: coerce in toWords (const notNullString = typeof inputString === 'string' ? inputString : String(inputString ?? '')) — one change covers every caller — or String(val) in WhereCell before calling toWords. Additionally, an error boundary around the preset cells would stop one bad preset from taking down the whole admin.
Link to the code that reproduces this issue

(create with npx create-payload-app@latest -t blank --db postgres; full repro below — happy to push a repo if needed)

Reproduction Steps
  1. Create a blank Payload app with @payloadcms/db-postgres (numeric IDs are required to trigger the bug; MongoDB string IDs mask it).
  2. Enable presets and add a hasMany relationship in payload.config.ts:
export default buildConfig({
  // ...
  queryPresets: {},
  collections: [
    {
      slug: 'posts',
      fields: [
        { name: 'title', type: 'text' },
        { name: 'authors', type: 'relationship', relationTo: 'users', hasMany: true },
      ],
    },
  ],
})
  1. Log in, create one post.
  2. In the Posts list view: open Filters → add filter → field Authors → operator equals → pick any user from the dropdown.
  3. Save this as a new preset ("New Preset").
  4. Reload the list view and open the preset selection list.
  5. → "Application error: a client-side exception has occurred", console shows TypeError: … .trim is not a function. The preset document's where in the DB contains {"authors":{"equals":[<number>]}}.
Which area(s) are affected?

area: ui

Environment Info
payload: 3.88.0 (also reproduced on 3.79.0)
@payloadcms/db-postgres: 3.88.0
next: 16.x
node: 24.11.1
OS: macOS (darwin arm64)

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.