Search & discovery
Product Filtering & Faceted Navigation
Generated from docs/plan/core/product-filtering-faceted-navigation/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A filtering panel on category pages that lets customers narrow product results by price, size, color, brand, and other attributes. Customers see facet counts (e.g., ‘Blue (23)’), can combine filters, and results update dynamically. Merchants reduce friction and help customers find what they want.
The problem
Customers land on a category page with 500 products and give up. They can’t filter by price, size, or color. Merchants lose sales because customers abandon before finding what they want. Competitors offer faceted navigation; it’s table stakes.
What it does
- Display available facets (filters) for each product category: price range, size, color, brand, and custom attributes
- Show facet count in parentheses (e.g., ‘Blue (23)’, ‘Size M (12)’) indicating how many products have that value
- Allow customers to select one or more facet values; filter products to show only those matching all selected facets (AND logic)
- Price facet: either range slider (min/max inputs) or predefined price ranges ($0-25, $26-50, $51-100, etc.)
- Facet persistence in URL: filter state is bookmarkable and shareable (e.g., ‘/products?category=shirts&color=blue&price=10-50’)
- Display facets in collapsible sections on desktop (click to expand/collapse); all expanded on mobile or in a modal
- Sort facet values by frequency (most products first) or alphabetically (configurable per facet)
- Clear individual facet selections with an X button; ‘Clear all filters’ button to reset
- Update facet counts dynamically as filters are applied (facet counts reflect current filters, not global)
- Facet labels are customizable by merchant (e.g., ‘Available Sizes’ instead of ‘Size’)
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 recommend filters to customers — that’s paid recommendation/ML module.
- Does not handle multi-select facet optimization (e.g., ‘Show only in-stock items in my selected size’) — that’s inventory real-time sync, separate.
- Does not support hierarchical facets (e.g., ‘Clothing > Tops > Shirts’) — that’s category hierarchy, separate feature.
- Does not do facet bundling/suggestions (e.g., ‘Customers often filter by color AND size together’) — that’s paid analytics.
- Does not handle facet dependencies (e.g., ‘XL size only available in solid colors, not patterns’) — too complex for core; merchants handle via inventory.
Data model
No new tables. Products table already contains attributes (as JSON or separate fields). Optional: ProductFacetCache table (shopId, categoryId, facetName, facetValues array, counts JSON) for performance caching. This cache is rebuilt daily or on product update. No migration required if using existing attributes.
API
- GET /categories/{id}/facets — returns available facets and their values
- GET /products?category=…&filter[color]=blue&filter[size]=M — filtered product list (uses AND logic)
- GET /products?category=…&filter[price]=10-50 — price range filter
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Category settings page with facet configuration section: (1) checkbox list of available attributes (color, size, price, brand, custom); (2) for each attribute, toggle ‘Make filterable?’; (3) label customization (e.g., ‘Available Sizes’ instead of ‘Size’); (4) sort order selector (frequency or alphabetical); (5) price range configuration if price facet is enabled (min/max bounds); (6) preview pane showing facets as customer would see them.
The seam — why this is core
Core owns: facet configuration, filter UI rendering, facet count calculation, AND logic for multi-facet filtering, URL persistence, dynamic count updates. No paid module.
Core owns filter schema, attribute indexing, and facet rendering; no separate module needed
Dependencies
- Products table with attributes/tags (existing)
- Category structure (existing)
- Product detail pages and filtering backend
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.
- Selecting facet value ‘Blue’ in color filter returns only products with attribute color=‘Blue’
- Facet count for ‘Blue’ shows correct count of products in current category with that color
- Price slider with min=10, max=100 returns only products priced $10-100 (inclusive)
- Combining 2 filters (color=Blue AND size=M) returns only products matching both (AND logic, not OR)
- URL with ?category=shirts&filter[color]=blue&filter[size]=M is bookmarkable; reloading returns same results
- Clearing all filters returns all products in the category
- Facet count for ‘Blue’ decreases when another filter is applied (reflects current state, not global counts)
- Facet values are sorted by frequency (most products first) or alphabetically as configured
Risks
If facet counts are stale (cached, not updated when products change), customer selects ‘Blue (23)’ but only 12 blue products appear (inventory change). If URL encoding breaks, filters are lost on bookmark/share. If AND logic is mistaken for OR, customer sees all blue products OR all size M products (wrong results). If price slider bounds are wrong (hardcoded $0-100 when products are $500-5000), slider is useless. If facets are not sorted by frequency, rare sizes are hard to find.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Same apps as search; free tier core feature |
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.