Search & discovery
Search Results Sorting
Generated from docs/plan/core/search-results-sorting/PLAN.md in the project's planning notes, which are not part of the public repository. Nothing described below is implemented — it is the written plan for it.
Committed, number 16 in the build order. Sort search results by price, newest or relevance. The whole list.
Search Results Sorting is a core feature that provides sort options for search results (price, newest, popularity, rating). It lets customers find products by preferred criteria, increasing conversion.
Partly built (2026-10-26). The core now ships sorting for product search and lists:
src/lib/commerce/product-sort.ts, tested intests/search-sort.test.mjs, throughGET /api/products?search=…&sort=. The sorts are relevance (the default), price up and down, newest, and name. Not built: popularity, rating and best-seller sorts; a merchant setting for the default or the enabled sorts, per content type, and its route; remembering the last sort in the session. The storefront keeps the sort in the URL instead. Different from this page: a sort by name ships, which this page had ruled out. This item stays planned until the rest is built or the plan is narrowed.
The problem
Customers search and can’t sort by price or popularity; they leave to check competitors.
What it does
- Sort by relevance (default), price (ascending/descending), newest, popularity, rating, best sellers
- Dropdown or button UI for sort selection with remember last sort (session storage)
- Admin setting: enable/disable sorts, default sort order (e.g., ‘relevance’)
- Mobile support: sort dropdown/modal
- Configurable per-content type (products, categories)
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Does NOT support custom sort fields (reason: limited to predefined)
- Does NOT track sort usage (reason: analytics feature)
- Does NOT support alphabetical sort (reason: low priority)
Data model
No new tables. New field: settings.search_default_sort (enum). New field: products.popularity_score (float, derived from views/purchases).
API
- GET /api/search?q=shirt&sort=price_asc
- GET /api/search?q=shirt&sort=popularity
- PATCH /api/settings/search-sorting {default_sort, enabled_sorts}
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Settings: default sort order, enable/disable sorts (subset of: relevance, price, newest, popularity, rating); preview showing results sorted by each.
The seam — why this is core
Core owns: sort API, sort options settings. Frontend owns: sort UI, session storage.
Core owns the interface + honest sort implementation; search UX is storefront infrastructure, not a support commitment or credential.
Dependencies
- Assumes search API exists
- Assumes product.popularity_score computable
- Assumes product.rating exists
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- GET /api/search?q=shirt&sort=price_asc returns low to high
- GET /api/search?q=shirt&sort=price_desc returns high to low
- GET /api/search?q=shirt&sort=newest returns by created_at descending
- GET /api/search?q=shirt&sort=rating returns by average rating descending
- GET /api/search?q=shirt&sort=popularity returns by view/purchase count descending
- Sort order changeable via admin
- Sort selection persists in session
- Mobile UI: sort dropdown/modal works on touch
- Default sort is ‘relevance’ (changeable)
- All storage drivers support equally
Risks
Popularity staleness: score outdated if not real-time. Rating bias: few 5-star ratings rank high. Performance: 100k+ products need pre-computed popularity_score. User confusion: too many sorts (>5) confuses customers.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Shopify: included (Search & Discovery); Magento: included |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and CONTRIBUTING.md for how to work on this repository.