Skip to content

feat(search): Context-aware Facet Values API — return only search-relevant facet options in sidebar dropdowns #12978

Description

@mekarpeles

Problem / Opportunity

Today when patrons search on openlibrary.org/search, the right-sidebar "Zoom In" section displays search facets (Language, Author, Subject, etc.) loaded asynchronously via /partials/SearchFacets.json (introduced in PR #9397 by @jimchamp). The current endpoint returns a full HTML partial for the entire sidebar — it does not expose individual facet field values as a JSON API.

As part of the new Search UX work (PR #12949 by @lokesh), individual facet fields are becoming interactive OlSelectPopover-based droppers. When a patron opens one of these droppers after performing a search, the options shown should reflect only the values relevant to that search — not every value known to the system.

Example: Searching "Lord of the Rings" and opening the Language dropper should show:

English   665
German     32
Spanish    18
French     15
Chinese    11

...not all 25+ languages in the system, many of which yield zero results for this query.

Why this matters: Showing irrelevant facet values steers patrons into dead ends (zero-result filtered pages) and degrades the search experience. Search-context-aware facets are a UX best practice used by major library and e-commerce platforms. With the new Search UX landing in #12949, this is the right moment to add the backing API.

Current UI for reference:

Image

Success: When a patron opens any facet dropper with an active search query, the options shown are limited to values with ≥1 matching result, ordered by count descending.

Proposal

Add a new Facet Values JSON API endpoint — e.g., GET /search/facets.json — that accepts a Solr field name plus the current search parameters, and returns an ordered list of {value, count} pairs scoped to that search context.

Proposed behavior:

  • With an active search query: Query Solr with facet=true&facet.field=<field>&rows=0 alongside the current search params; return only {value, count} pairs where count > 0, ordered by count descending.
  • Without an active search (e.g., homepage, empty query): Fall back to a sensible default — e.g., popular values, browser-locale language prioritized, etc.
  • Respects existing SFW filtering.

Example response for GET /search/facets.json?field=language&q=lord+of+the+rings:

[
  {"value": "English", "count": 665},
  {"value": "German",  "count": 32},
  {"value": "Spanish", "count": 18}
]
Implementation Details (for maintainers)

Summary

We'd continue to use search.json for search. There would be a second Facets JSON API (that defines responses/values for each facet type that gets triggered on-click of each Search UX Facet to populate its relevant, context-aware results)

Related files

Refer to this map of common Endpoints:

Requirements Checklist

Checklist of requirements that need to be satisfied in order for this issue to be closed:

  • New JSON endpoint (e.g. GET /search/facets.json) in FastAPI accepting field, q, and active facet filters as query params
  • Endpoint queries Solr with facet=true&facet.field=<field>&rows=0 alongside the current search params
  • Returns only values with count > 0, ordered by count descending, as [{value, count}] JSON
  • OlSelectPopover/OlFacetSelect components wire up to call the new endpoint when opening a dropper with an active search
  • Sensible fallback when no search query is present
  • SFW filter cookie is respected
  • Unit tests for the new endpoint

Stakeholders


Instructions for Contributors

Metadata

Metadata

Assignees

Labels

Affects: ExperienceIssues relating directly to service design & patrons experienceLead: @RayBBIssues overseen by Ray (Onboarding & Documentation Lead) [manages]Module: Search Page(s)Issues pertaining to the Search page UIPriority: 2Important, as time permits. [managed]Theme: SearchIssues related to search UI and backend. [managed]Type: Feature RequestIssue describes a feature or enhancement we'd like to implement. [managed]

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions