feat(admin-search)!: add baseFilter to scope search results per request - #201
Draft
jhb-dev wants to merge 5 commits into
Draft
feat(admin-search)!: add baseFilter to scope search results per request#201jhb-dev wants to merge 5 commits into
jhb-dev wants to merge 5 commits into
Conversation
The documented baseFilter recipe restricted results to `{ tenant: { equals:
tenant } }`, which also drops every indexed document that carries no tenant.
Search indexes are usually wider than the set of tenant-scoped collections, so
picking a tenant made shared collections disappear — in the dev app, every
author and media item. The README and the dev app now admit un-tenanted
documents via `or` + `exists: false`.
Also harden the surrounding code:
- A baseFilter that throws no longer fails the admin panel's render. It runs
during server render, so an exception replaced the whole page rather than
just the search. It narrows a query rather than granting access, so the
fallback is an unscoped search plus a logged error.
- Cover the config wiring the feature depends on: preserving an incoming
`custom` config, storing the options for the server component to read back,
and appending rather than replacing existing admin actions.
- Document that the resolved filter is readable in the browser, and that
mounting SearchWrapperClient by hand yields an unscoped search.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lth5KTeFjEugJcWXYUoKya
…ad already built The search component built its own request from `next/headers` via `createLocalReq`, but Payload builds one for this very render and passes it to admin components as a server prop. Its own copy therefore duplicated work and came out subtly different: `@payloadcms/next`'s `initReq` resolves the locale onto the request it builds, so the rebuilt one fell back to the default locale, and its i18n was the admin panel's client-facing subset cast to a server one. Take the request Payload passes instead. `next/headers`, `createLocalReq` and the i18n cast all go away, and a filter reading `req.locale` now sees the locale the panel is being viewed in. `req` is part of what Payload passes at runtime but not of its exported `ServerProps` type, so it is declared on the component's own props. If a future version stops passing it, a configured filter cannot run and the search would silently widen to every document — that case is logged rather than ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lth5KTeFjEugJcWXYUoKya
jhb-dev
force-pushed
the
feat/admin-search-base-filter
branch
from
August 23, 2026 20:17
75ec994 to
e006105
Compare
… and README example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
baseFilteroption (({ req }) => Where | Promise<Where>) that restricts search results to a constraint resolved against the current request. The filter runs on the server and is combined with the typed query usingand, so results stay in scope even before anything is typed.The main use is multi-tenancy: scoping the search to the tenant selected in the admin panel.
This scopes what the search offers, not what the API permits — it is not access control. The README points at
searchOverrides.access.readfor constrainingGET /api/searchserver-side.BREAKING
The header component is now a server component so it can evaluate
baseFilterbefore rendering. It moved:@jhb.software/payload-admin-search/client#SearchWrapper→@jhb.software/payload-admin-search/rsc#SearchWrapper/clientexport now providesSearchWrapperClientin place ofSearchWrapperConsumers must run
payload generate:importmapafter upgrading, otherwise the search component resolves to nothing and disappears from the admin header.Per the repo's breaking-change policy this is a
minorbump (plugin is pre-1.0).Tests
src/components/SearchWrapper/resolveBaseFilter.test.tssrc/components/SearchModal/buildSearchQuery.test.ts— the base filter and the typed query are combined withandVitest is newly configured for this plugin (
vitest.config.ts).Dev app demonstration
dev/gains atenantscollection, multi-tenant plugin wiring indev/src/payload.config.ts, abaseFilterexample in the plugin invocation, and seeded per-tenant documents. Switching tenants in the admin panel changes what the search returns.