Catalogue & product data
Faceted Navigation with Counts
Generated from docs/plan/core/faceted-navigation-with-counts/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Faceted search UI that shows available filter options (attributes and values) with result counts as customer filters by attributes. When customer selects ‘material: plastic’, they see ‘color: 12 options, size: 5 options, price: 8 ranges’ and only count for in-stock matching products. Turns product discovery from a static list into interactive exploration.
The problem
When I filter by ‘material: plastic’, customer still sees ‘color: red, blue, green, yellow (100 options)’ even though only 5 colors exist in plastic products. They also see colors that are out of stock. Facets should be smart: show only colors that exist in the filtered result, with live counts.
What it does
- Facet definition: select which attributes are facetable (brand, size, color, material, price range, etc.)
- Facet UI: display facets as checkboxes (multi-select) or radio buttons (single-select) per attribute
- Live facet counts: when customer selects ‘material: plastic’, counts update to show ‘color: 5 options’ (not 100)
- In-stock filtering: facets show only values for in-stock products (or hide counts for out-of-stock values)
- Price range facet: show discrete price ranges (€0-50, €50-100, €100-500) with counts per range
- Facet sorting: merchant can reorder facets (brand first, then price, then material) and reorder values within facet
- Facet hierarchy: optional parent-child facets (e.g., ‘Category’ parent with ‘Color’ child shown only when category is selected)
- Facet labels: customizable display labels (‘Brand’ vs. ‘Manufacturer’, ‘€50-100’ range vs. ‘Mid-Range’)
- Facet exclusions: merchant can hide certain facet values from UI (e.g., hide discontinued colors)
- Facet analytics: track facet clicks (which filters do customers use most?), click-to-purchase per facet
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.
- AI facet suggestions (auto-create facets from product data) — that is ML enrichment. Facets are merchant-defined.
- Facet-based rules (apply discount if facet_value = sale) — that is rule engine. Facets are search UI only.
- Multi-language facet labels — that is i18n module. Facets stored in one language.
- Dynamic facet ranking (ML learns which facets matter most) — that is ML/analytics. Facet order is merchant-defined.
- Facet A/B testing — that is experimentation platform. Core implements facets; A/B testing is paid.
Data model
New FACET table (facet_id, attribute_id, facet_type (checkbox|radio|range), sort_order, display_label). New FACET_VALUE_EXCLUSION table (facet_id, attribute_value, is_hidden). Search index must store attribute values for each product. Migration required: none (new tables).
API
- GET /admin/facets (list all facets)
- POST /admin/facets (create facet from attribute)
- PATCH /admin/facets/:id (update sort order, display label, exclusions)
- DELETE /admin/facets/:id (remove facet, products unaffected)
- GET /products?filters[material]=plastic&filters[color]=blue (product list with active facets)
- GET /products/facets?filters[material]=plastic (return available facets + counts for current filter state)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Facets page listing all facets with attribute mapping, type (checkbox/radio/range), sort order, and exclusions. Create facet button. Facet detail shows attribute values, sort order (drag-to-reorder), hide toggles per value, and label customization.
The seam — why this is core
Core owns facet schema, count aggregation, and filter logic. Core does not own AI facet suggestions (that is ML module) or facet-based rules (that is rule engine). Core owns the interface; paid modules consume it.
Depends on flexible attributes and indexing. Returns merchandise discovery value immediately. Table stakes for modern search UX.
Dependencies
- flexible-product-attributes (must exist first)
- Search/product indexing (core, assumed to exist)
- Inventory management (for stock filtering)
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.
- Facet ‘Material’ is created from attribute ‘material’ with values {plastic, metal, wood, rubber}
- Product list without filters shows all 4 material values with counts: plastic (150), metal (200), wood (50), rubber (30)
- Customer selects ‘material: plastic’; facet updates show color facet with 5 colors (not 100), each with new count
- Price range facet shows ranges: €0-50 (100 products), €50-100 (80 products), €100-500 (20 products)
- Out-of-stock products are excluded from facet counts (if filter setting is enabled)
- Facet value ‘color: obsolete_teal’ is hidden (toggled off); doesn’t appear in UI even though products exist
- Facet sort order is customized: Material, then Color, then Price (not alphabetical)
- Facet label customized: ‘material’ displays as ‘Material Type’, €0-50 range displays as ‘Budget-Friendly’
Risks
If facet counts are not live-updated when customer filters, they see stale counts (and confusion results). Mitigate: recalculate counts on every filter change (or use pre-computed facet cache updated on product changes). If facet index is not kept in sync with product inventory, out-of-stock products appear in facets. Mitigate: rebuild facet cache when inventory changes. If facet recursion is allowed (facet_child depends on facet_parent), cascading behavior is complex. Mitigate: flatten hierarchy or document parent-child resolution clearly.
Commercial context
| Suggested price | Free; core search UX |
| Rival anchor | Magento Open Source layered navigation; free |
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 ../../AI-GUIDE.md for how to work on this repository.