payloadcms / payloadcms/payload
filterOptions on a virtual: 'path.to.field' relationship/upload field is never applied in the list view's Where Builder filter dropdown
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
To Reproduce
- Define a collection with a
relationship(orupload) field that isvirtual: 'someRelation.someField'(i.e. a virtual field aliasing a field on a related collection, notvirtual: true), and that also defines afilterOptionsfunction, e.g.:
{
name: 'region',
type: 'relationship',
relationTo: 'regions',
virtual: 'customer.region',
filterOptions: async ({ req }) => {
// return a Where that should restrict the option list, e.g. scoped to the current user
return { id: { in: await getAllowedRegionIds(req) } }
},
}
- Go to the collection's list view, open the "Where" filter builder, add a condition on this field, and open its value dropdown.
- Observe the network request made by the value dropdown (
GET /api/<relationTo>?...) — it has nowhereconstraint at all, and the dropdown shows every document in the related collection, ignoringfilterOptions. - For comparison, a non-virtual relationship field with the same
filterOptionsfunction on the same collection filters correctly.
Describe the bug
resolveAllFilterOptions (packages/next/src/views/List/resolveAllFilterOptions.ts) computes the map key it stores each field's resolved filterOptions result under as:
const fieldPath = fieldAffectsData(field)
? pathPrefix ? `${pathPrefix}.${field.name}` : field.name
: pathPrefix
This always uses field.name, with no special case for virtual: string fields.
But reduceFieldsToOptions (packages/ui/src/utilities/reduceFieldsToOptions.tsx), which builds the field path actually used by the Where Builder's Condition component (both for the field-select dropdown's value and for the key looked up in the resolved-filter-options map), does special-case virtual: string fields — it uses the virtual path itself instead of the field's own name:
if ('virtual' in field && typeof field.virtual === 'string') {
pathPrefix = pathPrefix ? `${pathPrefix}.${field.virtual}` : field.virtual
if (fieldAffectsData(field)) {
shouldIgnoreFieldName = true // fieldPath ends up being just pathPrefix, i.e. field.virtual
}
}
So for a field like region with virtual: 'customer.region':
resolveAllFilterOptionsstores the resolvedWhereunder the key"region".reduceFieldsToOptions/Conditionlooks it up under the key"customer.region".
The lookup (resolvedFilterOptions?.get(fieldPath)) always misses, Condition passes filterOptions: undefined down to DefaultFilter → RelationshipFilter, and RelationshipFilter silently fetches the related collection with no where constraint at all — the filter is dropped with no error or warning.
This appears to affect any collection with a virtual: 'path' relationship/upload field that also uses filterOptions, regardless of database adapter.
Expected behavior
The value dropdown for a virtual: 'path' field with filterOptions should apply the same restriction that document-level relationship pickers for that field already correctly apply (that path works because it calls the field's filterOptions function directly, not through resolveAllFilterOptions's map).
Proposed fix
Make resolveAllFilterOptions's path computation match reduceFieldsToOptions's: when a field's virtual is a string and the field affects data, key the resolved map entry by the virtual path (composed with any pathPrefix) instead of field.name. We have a minimal patch doing exactly this that we've verified fixes the issue locally — happy to open a PR if useful.
Payload Version
payload@3.88.0, @payloadcms/next@3.88.0, @payloadcms/ui@3.88.0, Next.js 16.3.4 (Turbopack), Postgres adapter.
Link to the code that reproduces this issue
https://github.com/tramck/payload-virtual-filteroptions-repro
Reproduction Steps
- Clone the reproduction, then
pnpm install && pnpm dev(no.envneeded — the config has fallbacks, anonInitseed creates the data, andautoLoginis enabled). - Open http://localhost:3000/admin and go to the Orders list view.
- Click Filters → Add Filter.
- Set the field to Direct Region and open the value dropdown — ✅ only Allowed Region is offered.
- Change the field to Virtual Region and open the value dropdown — ❌ Allowed Region, Blocked Region A and Blocked Region B are all offered.
Which area(s) are affected?
area: ui
Environment Info
> payload-virtual-filteroptions-repro@1.0.0 payload /Users/travis/Documents/payload-virtual-filteroptions-repro
> cross-env NODE_OPTIONS=--no-deprecation payload info
Binaries:
Node: 26.0.0
npm: 11.12.1
Yarn: N/A
pnpm: 10.33.0
Relevant Packages:
payload: 3.90.1
next: 16.3.3
@payloadcms/db-sqlite: 3.90.1
@payloadcms/drizzle: 3.90.1
@payloadcms/graphql: 3.90.1
@payloadcms/next/utilities: 3.90.1
@payloadcms/richtext-lexical: 3.90.1
@payloadcms/translations: 3.90.1
@payloadcms/ui/shared: 3.90.1
react: 19.2.6
react-dom: 19.2.6
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0: Fri Jul 31 19:18:49 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6000
Available memory (MB): 16384
Available CPU cores: 8
Contributor 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.
Research direction
Start with packages/next/src/views/List/resolveAllFilterOptions.ts and compare its field-path handling with packages/ui/src/utilities/reduceFieldsToOptions.tsx. Run the linked reproduction and verify the Direct Region and Virtual Region dropdowns. Done means the virtual field's dropdown applies its filterOptions restriction and no longer lists blocked regions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100