AstroBaaS

Catalogue & product data

Faceted Navigation with Counts

Free — GPL coresize Mplanned, not built

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 priceFree; core search UX
Rival anchorMagento 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.